├── .gitignore ├── .poggit.yml ├── LICENSE ├── README.md ├── media └── hierarchy_banner.png ├── plugin.yml ├── resources ├── config.yml ├── mysql_stmts.sql └── sqlite_stmts.sql └── src └── CortexPE └── Hierarchy ├── EventListener.php ├── Hierarchy.php ├── command ├── HierarchyCommand.php ├── HierarchySubCommand.php ├── args │ ├── InfoTargetEnumArgument.php │ ├── MemberArgument.php │ ├── PermissionArgument.php │ ├── RoleArgument.php │ └── TargetEnumArgument.php └── subcommand │ ├── ACMemberRoleModifierCommand.php │ ├── ACPermissionModifierCommand.php │ ├── ClearAllCommand.php │ ├── CreateRoleCommand.php │ ├── DeleteRoleCommand.php │ ├── DenyPermCommand.php │ ├── FlushCommand.php │ ├── FormedCommand.php │ ├── GiveRoleCommand.php │ ├── GrantPermCommand.php │ ├── InfoCommand.php │ ├── RevokePermCommand.php │ ├── TakeRoleCommand.php │ └── TransferPrivilegesCommand.php ├── data ├── DataSource.php ├── legacy │ ├── IndexedLDR.php │ ├── JSONLDR.php │ ├── LegacyDataReader.php │ ├── MySQLLDR.php │ ├── SQLLDR.php │ ├── SQLiteLDR.php │ └── YAMLLDR.php ├── member │ ├── MemberDataSource.php │ ├── MySQLMemberDS.php │ ├── SQLMemberDS.php │ └── SQLiteMemberDS.php ├── migrator │ ├── BaseMigrator.php │ ├── DSMigrator.php │ ├── IndexedToSQL.php │ └── RolePositionSimplifier.php ├── role │ ├── IndexedRoleDS.php │ ├── JSONRoleDS.php │ ├── RoleDataSource.php │ └── YAMLRoleDS.php └── traits │ ├── IndexedDataUtilities.php │ ├── JSONProcessing.php │ └── YAMLProcessing.php ├── event ├── MemberRoleAddEvent.php ├── MemberRoleRemoveEvent.php └── MemberRoleUpdateEvent.php ├── exception ├── HierarchyException.php ├── RoleCollissionError.php ├── StartupFailureException.php ├── UnexpectedDataTypeError.php ├── UnknownPermissionNode.php └── UnresolvedRoleException.php ├── lang └── MessageStore.php ├── member ├── BaseMember.php ├── Member.php ├── MemberFactory.php └── OfflineMember.php ├── role ├── Role.php └── RoleManager.php └── task └── InvalidRolePermissionCheckTask.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # Poggit-CI Manifest. Open the CI at https://poggit.pmmp.io/ci/CortexPE/Hierarchy 2 | branches: 3 | - master 4 | projects: 5 | Hierarchy: 6 | path: "" 7 | libs: 8 | - src: poggit/libasynql/libasynql 9 | version: ^3.2.1 10 | - src: SOF3/await-generator/await-generator 11 | version: ^2.2.0 12 | - src: dktapps-pm-pl/pmforms/pmforms 13 | version: ^1.0.0 14 | - src: CortexPE/Commando/Commando 15 | version: ^2.1.0 16 | lint: 17 | phpstan: false 18 | ... 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Poggit](https://poggit.pmmp.io/ci.shield/CortexPE/Hierarchy/~)](https://poggit.pmmp.io/ci/CortexPE/Hierarchy/~) 4 | 5 | ## Features: 6 | | Feature | Hierarchy | PurePerms | 7 | | :-----: | :-------: | :-------: | 8 | | Multiple roles/groups per user | ✔️ | ❌ | 9 | | Allows same role/group names | ✔️ | ❌ | 10 | | Asynchronous Database I/O | ✔️ | ❌ | 11 | | MySQL DB Support | ✔️ | ✔️ | 12 | | SQLite3 DB Support | ✔️ | ✔️ | 13 | | YAML Storage Support | ✔️ | ✔️ | 14 | | JSON Storage Support | ✔️ | ❌ | 15 | | Per-Player-Permission | ✔️ | ✔️ | 16 | | Lightweight & concise | ✔️ | ❌ | 17 | | Extremely modular | ✔️ | ❌ | 18 | | In-game Form UI | ✔️ | ❌ | 19 | | In-game cmd argument list | ✔️ | ❌ | 20 | 21 | ### TODO (sorted by priority): 22 | - [ ] Documentation, documentation, DOCUMENTATION! 23 | - [ ] (Possibly) A WebGUI extension for ultra fanciness 24 | -------------------------------------------------------------------------------- /media/hierarchy_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CortexPE/Hierarchy/8ac34cc2be075e8a475b8ab4743aa3b51807f183/media/hierarchy_banner.png -------------------------------------------------------------------------------- /plugin.yml: -------------------------------------------------------------------------------- 1 | name: Hierarchy 2 | main: CortexPE\Hierarchy\Hierarchy 3 | version: 2.1.1 4 | api: 3.0.0 5 | load: POSTWORLD 6 | author: CortexPE 7 | description: "Role-based permission management system" 8 | website: "https://CortexPE.xyz" 9 | extensions: ["sqlite3"] 10 | permissions: 11 | hierarchy: 12 | default: "false" 13 | description: "Main Hierarchy root permission node" 14 | children: 15 | hierarchy.flush: 16 | default: "op" 17 | description: "Allows to use the flush command to save role configuration to disk" 18 | hierarchy.info: 19 | default: "op" 20 | description: "Allows to check hierarchy information" 21 | children: 22 | hierarchy.info.list_roles: 23 | default: "op" 24 | description: "Allows to check role listing" 25 | hierarchy.info.list_perms: 26 | default: "op" 27 | description: "Allows to check permission listing" 28 | hierarchy.info.member: 29 | default: "true" 30 | description: "Allows to check member information" 31 | hierarchy.info.role: 32 | default: "op" 33 | description: "Allows to check role information" 34 | hierarchy.role: 35 | default: "false" 36 | description: "Allows role commands" 37 | children: 38 | hierarchy.role.add_permission: 39 | default: "op" 40 | description: "Allows the adding of permissions to a role" 41 | hierarchy.role.deny_permission: 42 | default: "op" 43 | description: "Allows the denying of permissions from a role" 44 | hierarchy.role.remove_permission: 45 | default: "op" 46 | description: "Allows the removal of permissions from a role" 47 | hierarchy.role.create: 48 | default: "op" 49 | description: "Allows the creation of roles" 50 | hierarchy.role.delete: 51 | default: "op" 52 | description: "Allows the deletion of roles" 53 | hierarchy.role.give: 54 | default: "op" 55 | description: "Allows the giving of roles" 56 | hierarchy.role.take: 57 | default: "op" 58 | description: "Allows the sender to take roles" 59 | hierarchy.member: 60 | default: "false" 61 | description: "Allows member commands" 62 | children: 63 | hierarchy.member.add_permission: 64 | default: "op" 65 | description: "Allows the adding of permissions to a member" 66 | hierarchy.member.deny_permission: 67 | default: "op" 68 | description: "Allows the denying of permissions from a member" 69 | hierarchy.member.remove_permission: 70 | default: "op" 71 | description: "Allows the removal of permissions from a member" 72 | hierarchy.member.transfer_privileges: 73 | default: "op" 74 | description: "Allows to transfer privileges between two players" 75 | hierarchy.member.clear_all: 76 | default: "op" 77 | description: "Allows to remove all roles and permissions from a member" 78 | -------------------------------------------------------------------------------- /resources/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Hierarchy Role & Permission sets DataSource options & settings 3 | roleDataSource: 4 | # Available Types: json, yaml 5 | type: "yaml" 6 | 7 | # These settings will only apply when you chose json as dataSource type 8 | json: 9 | # Save JSON data files in a human-readable way 10 | prettyPrint: true 11 | 12 | # Hierarchy Member DataSource options & settings 13 | memberDataSource: 14 | # Available Types: json, sqlite3, mysql, yaml 15 | type: "sqlite3" 16 | 17 | # These settings will only apply when you chose sqlite3 as dataSource type 18 | sqlite3: 19 | # Database path 20 | dbPath: "members.sqlite" 21 | # The maximum number of simultaneous queries 22 | workerLimit: 1 23 | 24 | # These settings will only apply when you chose mysql as dataSource type 25 | mysql: 26 | host: "localhost" 27 | username: "hierarchy" 28 | password: "youShallNotPass" 29 | database: "hierarchyMembers" 30 | schema: "hierarchyMembers" 31 | # The maximum number of simultaneous queries 32 | workerLimit: 2 # Increase the value if you have a slow connection to the MySQL server 33 | 34 | # Allow OPs to bypass permission checks 35 | superAdminOPs: false 36 | 37 | configVersion: 1.6 38 | ... -------------------------------------------------------------------------------- /resources/mysql_stmts.sql: -------------------------------------------------------------------------------- 1 | -- # !mysql 2 | 3 | -- #{ hierarchy 4 | 5 | -- # { init 6 | -- # { rolesTable 7 | CREATE TABLE IF NOT EXISTS Roles 8 | ( 9 | ID INTEGER PRIMARY KEY AUTO_INCREMENT UNIQUE, 10 | Position INTEGER NOT NULL, 11 | Name VARCHAR(100) NOT NULL DEFAULT 'new role', 12 | isDefault BOOLEAN NOT NULL DEFAULT 0 13 | ); 14 | -- # } 15 | -- # { rolePermissionTable 16 | CREATE TABLE IF NOT EXISTS RolePermissions 17 | ( 18 | RoleID INTEGER NOT NULL, 19 | Permission VARCHAR(128) NOT NULL, 20 | PRIMARY KEY (RoleID, Permission) 21 | ); 22 | -- # } 23 | -- # { memberRolesTable 24 | CREATE TABLE IF NOT EXISTS MemberRoles 25 | ( 26 | Player VARCHAR(16) NOT NULL, -- MC Only allows IGNs upto 3-16 chars, case in-sensitive. 27 | RoleID INTEGER NOT NULL, 28 | AdditionalData VARCHAR(1024), 29 | PRIMARY KEY (Player, RoleID) 30 | ); 31 | -- # } 32 | -- # { memberPermissionsTable 33 | CREATE TABLE IF NOT EXISTS MemberPermissions 34 | ( 35 | Player VARCHAR(16) NOT NULL, -- MC Only allows IGNs upto 3-16 chars, case in-sensitive. 36 | Permission VARCHAR(128) NOT NULL, -- who tf has a permission node with 128 characters anyways? 37 | AdditionalData VARCHAR(1024), 38 | PRIMARY KEY (Player, Permission) 39 | ); 40 | -- # } 41 | -- # } 42 | 43 | -- # { check 44 | -- # { memberRoles_check1 45 | SELECT COUNT(*) as result 46 | FROM information_schema.COLUMNS 47 | WHERE TABLE_SCHEMA = DATABASE() 48 | AND TABLE_NAME = 'MemberRoles' 49 | AND COLUMN_NAME = 'AdditionalData'; 50 | -- # } 51 | -- # { memberPermissions_check1 52 | SELECT COUNT(*) as result 53 | FROM information_schema.COLUMNS 54 | WHERE TABLE_SCHEMA = DATABASE() 55 | AND TABLE_NAME = 'MemberPermissions' 56 | AND COLUMN_NAME = 'AdditionalData'; 57 | -- # } 58 | -- # } 59 | 60 | -- # { migrate 61 | -- # { memberRoles_patch1 62 | ALTER TABLE MemberRoles 63 | ADD COLUMN AdditionalData VARCHAR(1024); 64 | -- # } 65 | -- # { memberPermissions_patch1 66 | ALTER TABLE MemberPermissions 67 | ADD COLUMN AdditionalData VARCHAR(1024); 68 | -- # } 69 | -- # } 70 | 71 | -- # { drop 72 | -- # { rolesTable 73 | DROP TABLE Roles; 74 | -- # } 75 | -- # { rolePermissionTable 76 | DROP TABLE RolePermissions; 77 | -- # } 78 | -- # } 79 | 80 | -- # { member 81 | -- # { list 82 | SELECT Player 83 | FROM MemberRoles; 84 | -- # } 85 | -- # { roles 86 | -- # { get 87 | -- # :username string 88 | SELECT RoleID, AdditionalData 89 | FROM MemberRoles 90 | WHERE Player = :username; 91 | -- # } 92 | -- # { add 93 | -- # :username string 94 | -- # :role_id int 95 | REPLACE 96 | INTO MemberRoles (Player, RoleID) 97 | VALUES (:username, :role_id); 98 | -- # } 99 | -- # { remove 100 | -- # :username string 101 | -- # :role_id int 102 | DELETE 103 | FROM MemberRoles 104 | WHERE Player = :username 105 | AND RoleID = :role_id; 106 | -- # } 107 | -- # { transfer 108 | -- # :source string 109 | -- # :target string 110 | UPDATE MemberRoles 111 | SET Player = :target 112 | WHERE Player = :source; 113 | -- # } 114 | -- # { remove_all 115 | -- # :username string 116 | DELETE 117 | FROM MemberRoles 118 | WHERE Player = :username; 119 | -- # } 120 | -- # } 121 | -- # { permissions 122 | -- # { get 123 | -- # :username string 124 | SELECT Permission, AdditionalData 125 | FROM MemberPermissions 126 | WHERE Player = :username; 127 | -- # } 128 | -- # { add 129 | -- # :username string 130 | -- # :permission string 131 | REPLACE 132 | INTO MemberPermissions (Player, Permission) 133 | VALUES (:username, :permission); 134 | -- # } 135 | -- # { remove 136 | -- # :username string 137 | -- # :permission string 138 | DELETE 139 | FROM MemberPermissions 140 | WHERE Player = :username 141 | AND Permission LIKE CONCAT('%', :permission); 142 | -- # } 143 | -- # { transfer 144 | -- # :source string 145 | -- # :target string 146 | UPDATE MemberPermissions 147 | SET Player = :target 148 | WHERE Player = :source; 149 | -- # } 150 | -- # { remove_all 151 | -- # :username string 152 | DELETE 153 | FROM MemberPermissions 154 | WHERE Player = :username; 155 | -- # } 156 | -- # } 157 | -- # { etc 158 | -- # { update 159 | -- # { role 160 | -- # :username string 161 | -- # :role_id int 162 | -- # :additional_data string 163 | UPDATE MemberRoles 164 | SET AdditionalData = :additional_data 165 | WHERE Player = :username 166 | AND RoleID = :role_id; 167 | -- # } 168 | -- # { permission 169 | -- # :username string 170 | -- # :permission string 171 | -- # :additional_data string 172 | UPDATE MemberPermissions 173 | SET AdditionalData = :additional_data 174 | WHERE Player = :username 175 | AND Permission = :permission; 176 | -- # } 177 | -- # } 178 | -- # } 179 | -- # } 180 | 181 | -- # { role 182 | -- # { members 183 | -- # :role_id int 184 | SELECT Player 185 | FROM MemberRoles 186 | WHERE RoleID = :role_id; 187 | -- # } 188 | -- # { list 189 | SELECT * 190 | FROM Roles; 191 | -- # } 192 | -- # { create 193 | -- # :name string 194 | -- # :position int 195 | INSERT INTO Roles (Position, Name) 196 | VALUES (:position, :name); 197 | -- # } 198 | -- # { createDefault 199 | -- # :name string 200 | -- # :position int 201 | INSERT INTO Roles (Position, Name, isDefault) 202 | VALUES (:position, :name, 1); 203 | -- # } 204 | -- # { delete 205 | -- # :role_id int 206 | DELETE 207 | FROM Roles 208 | WHERE ID = :role_id; 209 | -- # } 210 | -- # { position 211 | -- # { shift 212 | -- # :offset int 213 | -- # :amount int 214 | UPDATE Roles 215 | SET Position = Position + :amount 216 | WHERE Position > :offset 217 | ORDER BY Position DESC; 218 | -- # } 219 | -- # } 220 | -- # { permissions 221 | -- # { get 222 | -- # :role_id int 223 | SELECT Permission 224 | FROM RolePermissions 225 | WHERE RoleID = :role_id; 226 | -- # } 227 | -- # { add 228 | -- # :role_id int 229 | -- # :permission string 230 | REPLACE 231 | INTO RolePermissions (RoleID, Permission) 232 | VALUES (:role_id, :permission); 233 | -- # } 234 | -- # { remove 235 | -- # :role_id int 236 | -- # :permission string 237 | DELETE 238 | FROM RolePermissions 239 | WHERE RoleID = :role_id 240 | AND Permission LIKE CONCAT('%', :permission); 241 | -- # } 242 | -- # } 243 | -- # } 244 | 245 | -- #} -------------------------------------------------------------------------------- /resources/sqlite_stmts.sql: -------------------------------------------------------------------------------- 1 | -- # !sqlite 2 | 3 | -- #{ hierarchy 4 | 5 | -- # { init 6 | -- # { rolesTable 7 | CREATE TABLE IF NOT EXISTS Roles 8 | ( 9 | ID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, 10 | Position INTEGER NOT NULL UNIQUE, 11 | Name VARCHAR(100) NOT NULL DEFAULT 'new role', 12 | isDefault BOOLEAN NOT NULL DEFAULT 0 13 | ); 14 | -- # } 15 | -- # { rolePermissionTable 16 | CREATE TABLE IF NOT EXISTS RolePermissions 17 | ( 18 | RoleID INTEGER NOT NULL, 19 | Permission TEXT NOT NULL, 20 | PRIMARY KEY (RoleID, Permission) 21 | ); 22 | -- # } 23 | -- # { memberRolesTable 24 | CREATE TABLE IF NOT EXISTS MemberRoles 25 | ( 26 | Player VARCHAR(16) NOT NULL COLLATE NOCASE, -- MC Only allows IGNs upto 3-16 chars, case in-sensitive. 27 | RoleID INTEGER NOT NULL, 28 | AdditionalData VARCHAR(1024), 29 | PRIMARY KEY (Player, RoleID) 30 | ); 31 | -- # } 32 | -- # { memberPermissionsTable 33 | CREATE TABLE IF NOT EXISTS MemberPermissions 34 | ( 35 | Player VARCHAR(16) NOT NULL COLLATE NOCASE, -- MC Only allows IGNs upto 3-16 chars, case in-sensitive. 36 | Permission VARCHAR(128) NOT NULL, -- who tf has a permission node with 128 characters anyways? 37 | AdditionalData VARCHAR(1024), 38 | PRIMARY KEY (Player, Permission) 39 | ); 40 | -- # } 41 | -- # } 42 | 43 | -- # { check 44 | -- # { memberRoles_check1 45 | SELECT COUNT(*) AS result 46 | FROM pragma_table_info('MemberRoles') 47 | WHERE name = 'AdditionalData'; 48 | -- # } 49 | -- # { memberPermissions_check1 50 | SELECT COUNT(*) AS result 51 | FROM pragma_table_info('MemberPermissions') 52 | WHERE name = 'AdditionalData'; 53 | -- # } 54 | -- # } 55 | 56 | -- # { migrate 57 | -- # { memberRoles_patch1 58 | ALTER TABLE MemberRoles 59 | ADD COLUMN AdditionalData VARCHAR(1024); 60 | -- # } 61 | -- # { memberPermissions_patch1 62 | ALTER TABLE MemberPermissions 63 | ADD COLUMN AdditionalData VARCHAR(1024); 64 | -- # } 65 | -- # } 66 | 67 | -- # { drop 68 | -- # { rolesTable 69 | DROP TABLE Roles; 70 | -- # } 71 | -- # { rolePermissionTable 72 | DROP TABLE RolePermissions; 73 | -- # } 74 | -- # } 75 | 76 | -- # { member 77 | -- # { list 78 | SELECT Player 79 | FROM MemberRoles; 80 | -- # } 81 | -- # { roles 82 | -- # { get 83 | -- # :username string 84 | SELECT RoleID, AdditionalData 85 | FROM MemberRoles 86 | WHERE Player = :username; 87 | -- # } 88 | -- # { add 89 | -- # :username string 90 | -- # :role_id int 91 | INSERT OR 92 | REPLACE 93 | INTO MemberRoles (Player, RoleID) 94 | VALUES (:username, :role_id); 95 | -- # } 96 | -- # { remove 97 | -- # :username string 98 | -- # :role_id int 99 | DELETE 100 | FROM MemberRoles 101 | WHERE Player = :username 102 | AND RoleID = :role_id; 103 | -- # } 104 | -- # { transfer 105 | -- # :source string 106 | -- # :target string 107 | UPDATE MemberRoles 108 | SET Player = :target 109 | WHERE Player = :source; 110 | -- # } 111 | -- # { remove_all 112 | -- # :username string 113 | DELETE 114 | FROM MemberRoles 115 | WHERE Player = :username; 116 | -- # } 117 | -- # } 118 | -- # { permissions 119 | -- # { get 120 | -- # :username string 121 | SELECT Permission, AdditionalData 122 | FROM MemberPermissions 123 | WHERE Player = :username; 124 | -- # } 125 | -- # { add 126 | -- # :username string 127 | -- # :permission string 128 | INSERT OR 129 | REPLACE 130 | INTO MemberPermissions (Player, Permission) 131 | VALUES (:username, :permission); 132 | -- # } 133 | -- # { remove 134 | -- # :username string 135 | -- # :permission string 136 | DELETE 137 | FROM MemberPermissions 138 | WHERE Player = :username 139 | AND Permission LIKE '%' || :permission; 140 | -- # } 141 | -- # { transfer 142 | -- # :source string 143 | -- # :target string 144 | UPDATE MemberPermissions 145 | SET Player = :target 146 | WHERE Player = :source; 147 | -- # } 148 | -- # { remove_all 149 | -- # :username string 150 | DELETE 151 | FROM MemberPermissions 152 | WHERE Player = :username; 153 | -- # } 154 | -- # } 155 | -- # { etc 156 | -- # { update 157 | -- # { role 158 | -- # :username string 159 | -- # :role_id int 160 | -- # :additional_data string 161 | UPDATE MemberRoles 162 | SET AdditionalData = :additional_data 163 | WHERE Player = :username 164 | AND RoleID = :role_id; 165 | -- # } 166 | -- # { permission 167 | -- # :username string 168 | -- # :permission string 169 | -- # :additional_data string 170 | UPDATE MemberPermissions 171 | SET AdditionalData = :additional_data 172 | WHERE Player = :username 173 | AND Permission = :permission; 174 | -- # } 175 | -- # } 176 | -- # } 177 | -- # } 178 | 179 | -- # { role 180 | -- # { members 181 | -- # :role_id int 182 | SELECT Player 183 | FROM MemberRoles 184 | WHERE RoleID = :role_id; 185 | -- # } 186 | -- # { list 187 | SELECT * 188 | FROM Roles; 189 | -- # } 190 | -- # { create 191 | -- # :name string 192 | -- # :position int 193 | INSERT INTO Roles (Position, Name) 194 | VALUES (:position, :name); 195 | -- # } 196 | -- # { createDefault 197 | -- # :name string 198 | -- # :position int 199 | INSERT INTO Roles (Position, Name, isDefault) 200 | VALUES (:position, :name, 1); 201 | -- # } 202 | -- # { delete 203 | -- # :role_id int 204 | DELETE 205 | FROM Roles 206 | WHERE ID = :role_id; 207 | -- # } 208 | -- # { position 209 | -- # { shift 210 | -- # :offset int 211 | -- # :amount int 212 | UPDATE Roles 213 | SET Position = -(Position + :amount) 214 | WHERE Position > :offset; 215 | -- # } 216 | -- # { invertSQLiteHack 217 | UPDATE Roles 218 | SET Position = -Position 219 | WHERE Position < 0; 220 | -- # } 221 | -- # } 222 | -- # { permissions 223 | -- # { get 224 | -- # :role_id int 225 | SELECT Permission 226 | FROM RolePermissions 227 | WHERE RoleID = :role_id; 228 | -- # } 229 | -- # { add 230 | -- # :role_id int 231 | -- # :permission string 232 | INSERT OR 233 | REPLACE 234 | INTO RolePermissions (RoleID, Permission) 235 | VALUES (:role_id, :permission); 236 | -- # } 237 | -- # { remove 238 | -- # :role_id int 239 | -- # :permission string 240 | DELETE 241 | FROM RolePermissions 242 | WHERE RoleID = :role_id 243 | AND Permission LIKE '%' || :permission; 244 | -- # } 245 | -- # } 246 | -- # } 247 | 248 | -- #} -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/EventListener.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy; 31 | 32 | 33 | use CortexPE\Hierarchy\member\MemberFactory; 34 | use pocketmine\event\Listener; 35 | use pocketmine\event\player\PlayerLoginEvent; 36 | use pocketmine\event\player\PlayerQuitEvent; 37 | 38 | class EventListener implements Listener { 39 | /** @var MemberFactory */ 40 | protected $memberFactory; 41 | 42 | public function __construct(Hierarchy $plugin) { 43 | $this->memberFactory = $plugin->getMemberFactory(); 44 | } 45 | 46 | /** 47 | * @param PlayerLoginEvent $ev 48 | * 49 | * @priority HIGHEST 50 | * @ignoreCancelled true 51 | */ 52 | public function onLogin(PlayerLoginEvent $ev) { 53 | $this->memberFactory->createSession($ev->getPlayer()); 54 | } 55 | 56 | /** 57 | * @param PlayerQuitEvent $ev 58 | * 59 | * @priority LOWEST 60 | */ 61 | public function onLeave(PlayerQuitEvent $ev) { 62 | $this->memberFactory->destroySession($ev->getPlayer()); 63 | } 64 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/Hierarchy.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy; 31 | 32 | use CortexPE\Commando\PacketHooker; 33 | use CortexPE\Hierarchy\command\HierarchyCommand; 34 | use CortexPE\Hierarchy\data\member\MemberDataSource; 35 | use CortexPE\Hierarchy\data\member\MySQLMemberDS; 36 | use CortexPE\Hierarchy\data\member\SQLiteMemberDS; 37 | use CortexPE\Hierarchy\data\migrator\DSMigrator; 38 | use CortexPE\Hierarchy\data\migrator\IndexedToSQL; 39 | use CortexPE\Hierarchy\data\migrator\RolePositionSimplifier; 40 | use CortexPE\Hierarchy\data\role\JSONRoleDS; 41 | use CortexPE\Hierarchy\data\role\RoleDataSource; 42 | use CortexPE\Hierarchy\data\role\YAMLRoleDS; 43 | use CortexPE\Hierarchy\exception\StartupFailureException; 44 | use CortexPE\Hierarchy\lang\MessageStore; 45 | use CortexPE\Hierarchy\member\MemberFactory; 46 | use CortexPE\Hierarchy\role\RoleManager; 47 | use CortexPE\Hierarchy\task\InvalidRolePermissionCheckTask; 48 | use Exception; 49 | use pocketmine\permission\DefaultPermissions; 50 | use pocketmine\plugin\PluginBase; 51 | use function extension_loaded; 52 | use function strtolower; 53 | 54 | class Hierarchy extends PluginBase { 55 | /** @var RoleDataSource */ 56 | private $roleDS; 57 | /** @var MemberDataSource */ 58 | private $memberDS; 59 | 60 | /** @var RoleManager */ 61 | private $roleManager; 62 | /** @var MemberFactory */ 63 | private $memberFactory; 64 | 65 | /** @var bool */ 66 | private $superAdminOPs = false; 67 | 68 | public function onEnable(): void { 69 | try{ 70 | DSMigrator::tryMigration($this); 71 | IndexedToSQL::tryMigration($this); 72 | RolePositionSimplifier::tryMigration($this); 73 | 74 | $this->saveResource("config.yml"); 75 | (new MessageStore($this->getDataFolder() . "messages.yml")); 76 | $conf = $this->getConfig(); 77 | 78 | $this->superAdminOPs = $conf->get("superAdminOPs", $this->superAdminOPs); 79 | 80 | switch($conf->getNested("roleDataSource.type", "yaml")) { 81 | case "json": 82 | $this->roleDS = new JSONRoleDS($this, $conf->getNested("roleDataSource.json")); 83 | break; 84 | case "yaml": 85 | $this->roleDS = new YAMLRoleDS($this); 86 | break; 87 | default: 88 | throw new StartupFailureException("Invalid role data source type, must be one of the following: 'json', 'yaml'"); 89 | } 90 | 91 | switch($conf->getNested("memberDataSource.type", "yaml")) { 92 | case "sqlite3": 93 | if(!extension_loaded("sqlite3")) { 94 | throw new StartupFailureException("SQLite3 PHP Extension is not enabled! Please check php.ini or choose a different data source type"); 95 | } 96 | $this->memberDS = new SQLiteMemberDS($this, $conf->getNested("memberDataSource.sqlite3")); 97 | break; 98 | case "mysql": 99 | if(!extension_loaded("mysqli")) { 100 | throw new StartupFailureException("MySQLi PHP Extension is not enabled! Please check php.ini or choose a different data source type"); 101 | } 102 | $this->memberDS = new MySQLMemberDS($this, $conf->getNested("memberDataSource.mysql")); 103 | break; 104 | 105 | default: 106 | throw new StartupFailureException("Invalid member data source type, must be one of the following: 'sqlite3', 'mysql'"); 107 | } 108 | 109 | DefaultPermissions::registerCorePermissions(); 110 | 111 | $this->roleManager = new RoleManager($this); 112 | $this->memberFactory = new MemberFactory($this); 113 | 114 | $this->roleDS->initialize(); 115 | $this->memberDS->initialize(); 116 | 117 | $this->getScheduler()->scheduleTask(new InvalidRolePermissionCheckTask($this)); 118 | } catch(Exception $e){ 119 | $this->getLogger()->logException($e); 120 | $this->getLogger()->warning("Forcefully shutting down server for the sake of security."); 121 | $this->getServer()->forceShutdown(); 122 | } 123 | } 124 | 125 | /** 126 | * @internal used to continue startup after the data source has finished initialization 127 | */ 128 | public function continueStartup(): void { 129 | $this->getServer()->getPluginManager()->registerEvents(new EventListener($this), $this); 130 | 131 | if(!PacketHooker::isRegistered()) { 132 | PacketHooker::register($this); 133 | } 134 | $this->getServer()->getCommandMap()->register( 135 | strtolower($this->getName()), 136 | new HierarchyCommand($this, "hrk", "Hierarchy main command") 137 | ); 138 | } 139 | 140 | public function onDisable(): void { 141 | if($this->memberFactory instanceof MemberFactory) { 142 | $this->memberFactory->shutdown(); 143 | } 144 | // last 145 | if($this->roleDS instanceof RoleDataSource) { 146 | $this->roleDS->shutdown(); 147 | } 148 | if($this->memberDS instanceof MemberDataSource) { 149 | $this->memberDS->shutdown(); 150 | } 151 | } 152 | 153 | /** 154 | * @return RoleDataSource 155 | */ 156 | public function getRoleDataSource(): RoleDataSource { 157 | return $this->roleDS; 158 | } 159 | 160 | /** 161 | * @return MemberDataSource 162 | */ 163 | public function getMemberDataSource(): ?MemberDataSource { 164 | return $this->memberDS; 165 | } 166 | 167 | /** 168 | * @return RoleManager 169 | */ 170 | public function getRoleManager(): RoleManager { 171 | return $this->roleManager; 172 | } 173 | 174 | /** 175 | * 176 | * @return MemberFactory 177 | */ 178 | public function getMemberFactory(): MemberFactory { 179 | return $this->memberFactory; 180 | } 181 | 182 | /** 183 | * @return bool 184 | */ 185 | public function isSuperAdminOPs(): bool { 186 | return $this->superAdminOPs; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/HierarchyCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command; 31 | 32 | 33 | use CortexPE\Commando\BaseCommand; 34 | use CortexPE\Hierarchy\command\subcommand\ClearAllCommand; 35 | use CortexPE\Hierarchy\command\subcommand\CreateRoleCommand; 36 | use CortexPE\Hierarchy\command\subcommand\DeleteRoleCommand; 37 | use CortexPE\Hierarchy\command\subcommand\DenyPermCommand; 38 | use CortexPE\Hierarchy\command\subcommand\FlushCommand; 39 | use CortexPE\Hierarchy\command\subcommand\GiveRoleCommand; 40 | use CortexPE\Hierarchy\command\subcommand\GrantPermCommand; 41 | use CortexPE\Hierarchy\command\subcommand\InfoCommand; 42 | use CortexPE\Hierarchy\command\subcommand\RevokePermCommand; 43 | use CortexPE\Hierarchy\command\subcommand\TakeRoleCommand; 44 | use CortexPE\Hierarchy\command\subcommand\TransferPrivilegesCommand; 45 | use CortexPE\Hierarchy\Hierarchy; 46 | use pocketmine\command\CommandSender; 47 | 48 | class HierarchyCommand extends BaseCommand { 49 | /** @var Hierarchy */ 50 | private $plugin; 51 | 52 | public function __construct(Hierarchy $plugin, string $name, string $description = "", array $aliases = []) { 53 | $this->plugin = $plugin; 54 | parent::__construct($plugin, $name, $description, $aliases); 55 | } 56 | 57 | protected function prepare(): void { 58 | $this->registerSubCommand( 59 | new CreateRoleCommand( 60 | $this->plugin, 61 | "createrole", 62 | "Create a new Role" 63 | ) 64 | ); 65 | $this->registerSubCommand( 66 | new GiveRoleCommand( 67 | $this->plugin, 68 | "giverole", 69 | "Give role to member" 70 | ) 71 | ); 72 | $this->registerSubCommand( 73 | new TakeRoleCommand( 74 | $this->plugin, 75 | "takerole", 76 | "Take role from member" 77 | ) 78 | ); 79 | $this->registerSubCommand( 80 | new DeleteRoleCommand( 81 | $this->plugin, 82 | "deleterole", 83 | "Delete a role" 84 | ) 85 | ); 86 | $this->registerSubCommand( 87 | new GrantPermCommand( 88 | $this->plugin, 89 | "grantperm", 90 | "Grant permission to role/member" 91 | ) 92 | ); 93 | $this->registerSubCommand( 94 | new DenyPermCommand( 95 | $this->plugin, 96 | "denyperm", 97 | "Deny permission from role/member" 98 | ) 99 | ); 100 | $this->registerSubCommand( 101 | new RevokePermCommand( 102 | $this->plugin, 103 | "revokeperm", 104 | "Revoke or remove permission from role/member", 105 | ["removeperm"] 106 | ) 107 | ); 108 | $this->registerSubCommand( 109 | new FlushCommand( 110 | $this->plugin, 111 | "flush", 112 | "Save role configuration to disk (if applicable)", 113 | ["save"] 114 | ) 115 | ); 116 | $this->registerSubCommand( 117 | new InfoCommand( 118 | $this->plugin, 119 | "info", 120 | "Get the role list and role or member information", 121 | ["i"] 122 | ) 123 | ); 124 | $this->registerSubCommand( 125 | new TransferPrivilegesCommand( 126 | $this->plugin, 127 | "transferprvlgs", 128 | "Transfer privileges between players" 129 | ) 130 | ); 131 | $this->registerSubCommand( 132 | new ClearAllCommand( 133 | $this->plugin, 134 | "clearall", 135 | "Clear all roles and permissions of a players" 136 | ) 137 | ); 138 | } 139 | 140 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 141 | $this->sendUsage(); 142 | } 143 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/HierarchySubCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command; 31 | 32 | 33 | use CortexPE\Commando\BaseSubCommand; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | use CortexPE\Hierarchy\lang\MessageStore; 36 | use CortexPE\Hierarchy\member\BaseMember; 37 | use CortexPE\Hierarchy\member\MemberFactory; 38 | use CortexPE\Hierarchy\role\Role; 39 | use CortexPE\Hierarchy\role\RoleManager; 40 | use pocketmine\permission\Permission; 41 | use pocketmine\Player; 42 | use pocketmine\utils\TextFormat; 43 | 44 | abstract class HierarchySubCommand extends BaseSubCommand { 45 | /** @var Hierarchy */ 46 | protected $plugin; 47 | /** @var RoleManager */ 48 | protected $roleManager; 49 | /** @var MemberFactory */ 50 | protected $memberFactory; 51 | /** @var bool */ 52 | protected $opBypass; 53 | 54 | public function __construct(Hierarchy $plugin, string $name, string $description = "", array $aliases = []) { 55 | parent::__construct($name, $description, $aliases); 56 | $this->plugin = $plugin; 57 | $this->roleManager = $plugin->getRoleManager(); 58 | $this->memberFactory = $plugin->getMemberFactory(); 59 | $this->opBypass = $plugin->isSuperAdminOPs(); 60 | } 61 | 62 | protected function sendFormattedMessage(string $key, array $arguments = []): void { 63 | $this->currentSender->sendMessage(MessageStore::getMessage($key, $arguments)); 64 | } 65 | 66 | protected function isSenderInGame(): bool { 67 | return $this->currentSender instanceof Player; 68 | } 69 | 70 | /** 71 | * @param BaseMember|Role $target 72 | * @param Permission|string $permission 73 | * 74 | * @return bool 75 | */ 76 | protected function isSenderHigher($target, $permission = null): bool { 77 | if(!$this->isSenderInGame()) { 78 | return true; 79 | } 80 | if($this->currentSender->isOp() && $this->opBypass) { 81 | return true; 82 | } 83 | if($permission === null) { 84 | if($target instanceof BaseMember) { 85 | $targetPos = $target->getTopRole()->getPosition(); 86 | } elseif($target instanceof Role) { 87 | $targetPos = $target->getPosition(); 88 | } else { 89 | throw new \InvalidArgumentException("Passed argument is neither a Role nor a Member"); 90 | } 91 | $senderPos = $this->memberFactory->getMember($this->currentSender)->getTopRole()->getPosition(); 92 | } else { 93 | if($target instanceof BaseMember) { 94 | $tTopRole = $target->getTopRoleWithPermission($permission); 95 | if($tTopRole === null)return true; // target does not have a role with that specific perm 96 | $targetPos = $tTopRole->getPosition(); 97 | } else { 98 | throw new \InvalidArgumentException("Passed argument is not a Member"); 99 | } 100 | $sTopRole = $this->memberFactory->getMember($this->currentSender)->getTopRoleWithPermission($permission); 101 | if($tTopRole === null)return false; // sender does not have a role with that specific perm 102 | $senderPos = $sTopRole->getPosition(); 103 | } 104 | 105 | return $senderPos > $targetPos; 106 | } 107 | 108 | /** 109 | * @param BaseMember|Role $target 110 | * @param Permission|string $permission 111 | * 112 | * @return bool 113 | */ 114 | protected function doHierarchyPositionCheck($target, $permission = null): bool { 115 | if(!($valid = $this->isSenderHigher($target, $permission))) { 116 | $this->sendFormattedMessage("err.target_higher_hrk", [ 117 | "target" => $target->getName() 118 | ]); 119 | } 120 | 121 | return $valid; 122 | } 123 | 124 | protected function getSenderMember(): BaseMember { 125 | return $this->memberFactory->getMember($this->currentSender); 126 | } 127 | 128 | protected function isSenderInGameNoArguments(array $args):bool { 129 | return $this->isSenderInGame() && empty($args); 130 | } 131 | 132 | protected function sendPermissionError():void { 133 | $this->currentSender->sendMessage( 134 | $this->currentSender->getServer()->getLanguage()->translateString( 135 | TextFormat::RED . "%commands.generic.permission" 136 | ) 137 | ); 138 | } 139 | 140 | protected function getRolesApplicable(&$roles, &$roles_i):bool { 141 | $roles = []; 142 | $roles_i = []; 143 | foreach($this->roleManager->getRoles() as $role) { 144 | if($role->isDefault() || !$this->isSenderHigher($role)) { 145 | continue; 146 | } 147 | $roles[] = "{$role->getName()} ({$role->getId()})"; 148 | $roles_i[] = $role->getId(); 149 | } 150 | if(empty($roles)) { 151 | $this->sendFormattedMessage("err.no_roles_for_action"); 152 | 153 | return false; 154 | } 155 | return true; 156 | } 157 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/args/InfoTargetEnumArgument.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\args; 31 | 32 | 33 | use CortexPE\Commando\args\StringEnumArgument; 34 | use pocketmine\command\CommandSender; 35 | 36 | class InfoTargetEnumArgument extends StringEnumArgument { 37 | public const TARGET_MEMBER = "member"; 38 | public const TARGET_ROLE = "role"; 39 | public const TARGET_ROLE_LIST = "role_list"; 40 | public const TARGET_PERM_LIST = "perm_list"; 41 | protected const VALUES = [ 42 | "member" => self::TARGET_MEMBER, 43 | "role" => self::TARGET_ROLE, 44 | "role_list" => self::TARGET_ROLE_LIST, 45 | "perm_list" => self::TARGET_PERM_LIST, 46 | ]; 47 | 48 | public function parse(string $argument, CommandSender $sender) { 49 | return (string)$this->getValue($argument); 50 | } 51 | 52 | public function getTypeName(): string { 53 | return "infoTarget"; 54 | } 55 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/args/MemberArgument.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\args; 31 | 32 | 33 | use CortexPE\Commando\args\BaseArgument; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | use CortexPE\Hierarchy\member\MemberFactory; 36 | use pocketmine\command\CommandSender; 37 | use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; 38 | use pocketmine\Server; 39 | use function preg_match; 40 | 41 | class MemberArgument extends BaseArgument { 42 | /** @var MemberFactory */ 43 | protected $mFac; 44 | 45 | public function __construct(string $name, bool $optional) { 46 | parent::__construct($name, $optional); 47 | /** @var Hierarchy $hrk */ 48 | $hrk = Server::getInstance()->getPluginManager()->getPlugin("Hierarchy"); 49 | $this->mFac = $hrk->getMemberFactory(); 50 | } 51 | 52 | public function getNetworkType(): int { 53 | return AvailableCommandsPacket::ARG_TYPE_TARGET; 54 | } 55 | 56 | public function canParse(string $testString, CommandSender $sender): bool { 57 | // PM player username validity regex 58 | return (bool)preg_match("/^(?!rcon|console)[a-zA-Z0-9_ ]{1,16}$/i", $testString); 59 | } 60 | 61 | public function parse(string $argument, CommandSender $sender) { 62 | return $this->mFac->getMember($argument); 63 | } 64 | 65 | public function getTypeName(): string { 66 | return "member"; 67 | } 68 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/args/PermissionArgument.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\args; 31 | 32 | 33 | use CortexPE\Commando\args\RawStringArgument; 34 | use pocketmine\command\CommandSender; 35 | use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; 36 | use pocketmine\permission\PermissionManager; 37 | use function preg_match; 38 | 39 | class PermissionArgument extends RawStringArgument { 40 | /** @var PermissionManager */ 41 | protected $pMgr; 42 | 43 | public function __construct(string $name, bool $optional) { 44 | parent::__construct($name, $optional); 45 | $this->pMgr = PermissionManager::getInstance(); 46 | } 47 | 48 | public function getNetworkType(): int { 49 | return AvailableCommandsPacket::ARG_TYPE_STRING; 50 | } 51 | 52 | public function canParse(string $testString, CommandSender $sender): bool { 53 | return (bool)preg_match("/^(?:\w+|\.\w+)+$/", $testString); 54 | } 55 | 56 | public function parse(string $argument, CommandSender $sender) { 57 | return $this->pMgr->getPermission($argument); 58 | } 59 | 60 | public function getTypeName(): string { 61 | return "permission"; 62 | } 63 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/args/RoleArgument.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\args; 31 | 32 | 33 | use CortexPE\Commando\args\RawStringArgument; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | use CortexPE\Hierarchy\role\RoleManager; 36 | use pocketmine\command\CommandSender; 37 | use pocketmine\Server; 38 | 39 | class RoleArgument extends RawStringArgument { 40 | /** @var RoleManager */ 41 | protected $roleMgr; 42 | 43 | public function __construct(string $name, bool $optional) { 44 | parent::__construct($name, $optional); 45 | /** @var Hierarchy $hrk */ 46 | $hrk = Server::getInstance()->getPluginManager()->getPlugin("Hierarchy"); 47 | $this->roleMgr = $hrk->getRoleManager(); 48 | } 49 | 50 | public function parse(string $argument, CommandSender $sender) { 51 | return $this->roleMgr->getRoleByName($argument); 52 | } 53 | 54 | public function canParse(string $testString, CommandSender $sender): bool { 55 | return $this->parse($testString, $sender) !== null; 56 | } 57 | 58 | public function getTypeName(): string { 59 | return "role"; 60 | } 61 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/args/TargetEnumArgument.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\args; 31 | 32 | 33 | use CortexPE\Commando\args\StringEnumArgument; 34 | use pocketmine\command\CommandSender; 35 | 36 | class TargetEnumArgument extends StringEnumArgument { 37 | public const TARGET_MEMBER = "member"; 38 | public const TARGET_ROLE = "role"; 39 | protected const VALUES = [ 40 | "member" => self::TARGET_MEMBER, 41 | "role" => self::TARGET_ROLE, 42 | ]; 43 | 44 | public function parse(string $argument, CommandSender $sender) { 45 | return (string)$this->getValue($argument); 46 | } 47 | 48 | public function getTypeName(): string { 49 | return "target"; 50 | } 51 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/ACMemberRoleModifierCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Commando\args\BooleanArgument; 34 | use CortexPE\Commando\BaseCommand; 35 | use CortexPE\Hierarchy\command\args\MemberArgument; 36 | use CortexPE\Hierarchy\command\args\RoleArgument; 37 | use CortexPE\Hierarchy\command\HierarchySubCommand; 38 | use CortexPE\Hierarchy\member\BaseMember; 39 | use CortexPE\Hierarchy\role\Role; 40 | use dktapps\pmforms\CustomForm; 41 | use dktapps\pmforms\CustomFormResponse; 42 | use dktapps\pmforms\element\Dropdown; 43 | use dktapps\pmforms\element\Input; 44 | use dktapps\pmforms\element\Label; 45 | use dktapps\pmforms\element\Toggle; 46 | use pocketmine\command\CommandSender; 47 | use pocketmine\Player; 48 | use function count; 49 | 50 | abstract class ACMemberRoleModifierCommand extends HierarchySubCommand implements FormedCommand { 51 | protected const CHILD_PERMISSION = null; 52 | protected const MESSAGE_ROOT = null; 53 | 54 | protected function prepare(): void { 55 | $this->registerArgument(0, new MemberArgument("member", true)); 56 | $this->registerArgument(1, new RoleArgument("role", true)); 57 | $this->registerArgument(2, new BooleanArgument("temporary", true)); 58 | $this->setPermission("hierarchy;hierarchy.role;hierarchy.role." . static::CHILD_PERMISSION); 59 | } 60 | 61 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 62 | if($this->isSenderInGameNoArguments($args)) { 63 | $this->sendForm(); 64 | 65 | return; 66 | } elseif(count($args) < 2) { 67 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 68 | 69 | return; 70 | } 71 | 72 | /** @var BaseMember $member */ 73 | $member = $args["member"]; 74 | /** @var Role|null $role */ 75 | $role = $args["role"]; 76 | 77 | if($role instanceof Role) { 78 | if(!$this->doHierarchyPositionCheck($member) || !$this->doHierarchyPositionCheck($role)) { 79 | return; 80 | } 81 | 82 | $formats = [ 83 | "member" => $member->getName(), 84 | "role" => $role->getName(), 85 | "id" => $role->getId() 86 | ]; 87 | 88 | if(!$role->isDefault()) { 89 | $this->doOperationOnMember($member, $role, ($args["temporary"] ?? false), $formats); 90 | } else { 91 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".default", $formats); 92 | } 93 | } else { 94 | $this->sendFormattedMessage("err.unknown_role"); 95 | } 96 | } 97 | 98 | public function sendForm(): void { 99 | if($this->currentSender instanceof Player) { 100 | if(!$this->getRolesApplicable($roles, $roles_i)) { 101 | return; 102 | } 103 | $this->currentSender->sendForm(new CustomForm($this->plugin->getName(), [ 104 | new Label("description", $this->getDescription()), 105 | new Input("member", "Member", "Member Name"), 106 | new Dropdown("role", "Role", $roles), 107 | new Toggle("temporary", "Temporary", false) 108 | ], function (Player $player, CustomFormResponse $response) use ($roles, $roles_i): void { 109 | $this->setCurrentSender($player); 110 | $this->onRun($player, $this->getName(), [ 111 | "member" => $this->memberFactory->getMember($response->getString("member")), 112 | "role" => $this->roleManager->getRole($roles_i[$response->getInt("role")]), 113 | "temporary" => $response->getBool("temporary") 114 | ]); 115 | })); 116 | } 117 | } 118 | 119 | abstract protected function doOperationOnMember(BaseMember $member, Role $role, bool $temporary, array $msgFormats): void; 120 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/ACPermissionModifierCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Commando\BaseCommand; 34 | use CortexPE\Hierarchy\command\args\MemberArgument; 35 | use CortexPE\Hierarchy\command\args\PermissionArgument; 36 | use CortexPE\Hierarchy\command\args\RoleArgument; 37 | use CortexPE\Hierarchy\command\args\TargetEnumArgument; 38 | use CortexPE\Hierarchy\command\HierarchySubCommand; 39 | use CortexPE\Hierarchy\lang\MessageStore; 40 | use CortexPE\Hierarchy\member\BaseMember; 41 | use CortexPE\Hierarchy\role\Role; 42 | use dktapps\pmforms\CustomForm; 43 | use dktapps\pmforms\CustomFormResponse; 44 | use dktapps\pmforms\element\Dropdown; 45 | use dktapps\pmforms\element\Input; 46 | use dktapps\pmforms\element\Label; 47 | use dktapps\pmforms\ModalForm; 48 | use pocketmine\command\CommandSender; 49 | use pocketmine\permission\Permission; 50 | use pocketmine\permission\PermissionManager; 51 | use pocketmine\Player; 52 | use function count; 53 | use function implode; 54 | 55 | abstract class ACPermissionModifierCommand extends HierarchySubCommand implements FormedCommand { 56 | protected const CHILD_PERMISSION = null; 57 | protected const MESSAGE_ROOT = null; 58 | 59 | protected function prepare(): void { 60 | $this->registerArgument(0, new TargetEnumArgument("targetType", true)); 61 | $this->registerArgument(1, new RoleArgument("targetRole", true)); 62 | $this->registerArgument(1, new MemberArgument("targetMember", true)); 63 | $this->registerArgument(2, new PermissionArgument("permission", true)); 64 | $this->setPermission(implode(";", [ 65 | "hierarchy", 66 | "hierarchy.role", 67 | "hierarchy.role." . static::CHILD_PERMISSION, 68 | "hierarchy.member", 69 | "hierarchy.member." . static::CHILD_PERMISSION 70 | ])); 71 | } 72 | 73 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 74 | if($this->isSenderInGameNoArguments($args)) { 75 | $this->sendForm(); 76 | 77 | return; 78 | } elseif(count($args) < 3) { 79 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 80 | 81 | return; 82 | } 83 | 84 | /** @var Permission|null $permission */ 85 | $permission = $args["permission"]; 86 | 87 | if($permission instanceof Permission) { 88 | switch($args["targetType"] ?? "undefined") { 89 | case TargetEnumArgument::TARGET_MEMBER: 90 | $target = $args["targetMember"]; 91 | if($target instanceof BaseMember) { 92 | if($sender->hasPermission("hierarchy.member." . static::CHILD_PERMISSION)) { 93 | if($this->doHierarchyPositionCheck($target, $permission)) { 94 | $this->doOperationOnMember($target, $permission); 95 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".member.success", [ 96 | "permission" => $permission->getName(), 97 | "member" => $target->getName() 98 | ]); 99 | } else { 100 | $this->sendFormattedMessage("err.target_higher_hrk"); 101 | } 102 | } else { 103 | $this->sendPermissionError(); 104 | } 105 | break; 106 | } 107 | break; 108 | case TargetEnumArgument::TARGET_ROLE: 109 | $target = $args["targetRole"]; 110 | if($target instanceof Role) { 111 | if($sender->hasPermission("hierarchy.role." . static::CHILD_PERMISSION)) { 112 | if($this->doHierarchyPositionCheck($target)) { 113 | $this->doOperationOnRole($target, $permission); 114 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".role.success", [ 115 | "permission" => $permission->getName(), 116 | "role" => $target->getName(), 117 | "role_id" => $target->getId() 118 | ]); 119 | } else { 120 | $this->sendFormattedMessage("err.target_higher_hrk"); 121 | } 122 | } else { 123 | $this->sendPermissionError(); 124 | } 125 | break; 126 | } 127 | break; 128 | } 129 | } else { 130 | $this->sendFormattedMessage("err.unknown_permission"); 131 | } 132 | } 133 | 134 | public function sendForm(): void { 135 | if($this->currentSender instanceof Player) { 136 | $this->currentSender->sendForm(new ModalForm( 137 | $this->plugin->getName(), 138 | MessageStore::getMessage("cmd." . static::MESSAGE_ROOT . ".form.choose_type"), 139 | function(Player $player, bool $choice): void { 140 | if($choice) { 141 | $this->setCurrentSender($player); 142 | if(!$this->getRolesApplicable($roles, $roles_i)) { 143 | return; 144 | } 145 | $player->sendForm(new CustomForm($this->plugin->getName(), [ 146 | new Label("description", $this->getDescription()), 147 | new Dropdown("role", "Role", $roles), 148 | new Input("permission", "Permission"), 149 | ], function(Player $player, CustomFormResponse $response) use ($roles, $roles_i): void { 150 | $this->setCurrentSender($player); 151 | $this->onRun($player, $this->getName(), [ 152 | "targetType" => TargetEnumArgument::TARGET_ROLE, 153 | "target" => $this->roleManager->getRole($roles_i[$response->getInt("role")]), 154 | "permission" => PermissionManager::getInstance() 155 | ->getPermission($response->getString("permission")), 156 | ]); 157 | })); 158 | } else { 159 | $player->sendForm(new CustomForm($this->plugin->getName(), [ 160 | new Label("description", $this->getDescription()), 161 | new Input("member", "Member"), 162 | new Input("permission", "Permission"), 163 | ], function(Player $player, CustomFormResponse $response): void { 164 | $this->setCurrentSender($player); 165 | $this->onRun($player, $this->getName(), [ 166 | "targetType" => TargetEnumArgument::TARGET_MEMBER, 167 | "target" => $this->memberFactory->getMember($response->getString("member")), 168 | "permission" => PermissionManager::getInstance() 169 | ->getPermission($response->getString("permission")), 170 | ]); 171 | })); 172 | } 173 | }, 174 | MessageStore::getMessage("cmd." . static::MESSAGE_ROOT . ".form.type_role"), 175 | MessageStore::getMessage("cmd." . static::MESSAGE_ROOT . ".form.type_member") 176 | )); 177 | } 178 | } 179 | 180 | abstract protected function doOperationOnRole(Role $member, Permission $permission): void; 181 | 182 | abstract protected function doOperationOnMember(BaseMember $member, Permission $permission): void; 183 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/ClearAllCommand.php: -------------------------------------------------------------------------------- 1 | registerArgument(0, new MemberArgument("targetMember", false)); 23 | $this->setPermission(implode(";", [ 24 | "hierarchy", 25 | "hierarchy.member", 26 | "hierarchy.member.clear_all" 27 | ])); 28 | } 29 | 30 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 31 | if($this->isSenderInGameNoArguments($args)) { 32 | $this->sendForm(); 33 | 34 | return; 35 | } elseif(count($args) < 1) { 36 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 37 | 38 | return; 39 | } 40 | /** @var BaseMember $target */ 41 | $target = $args["targetMember"]; 42 | 43 | if(!$this->doHierarchyPositionCheck($target)) { 44 | return; 45 | } 46 | 47 | foreach($target->getRoles() as $role) { 48 | if($role->isDefault())continue; 49 | $target->removeRole($role, true, false); 50 | } 51 | foreach($target->getMemberPermissions() as $permission => $value){ 52 | $target->removeMemberPermission($permission, true, false); 53 | } 54 | $this->plugin->getMemberDataSource()->updateMemberData($target, MemberDataSource::ACTION_MEMBER_ROLE_REMOVE_ALL); 55 | $this->plugin->getMemberDataSource()->updateMemberData($target, MemberDataSource::ACTION_MEMBER_PERMS_REMOVE_ALL); 56 | $this->sendFormattedMessage("cmd.clear_all.success", [ 57 | "target" => $target->getName() 58 | ]); 59 | } 60 | 61 | public function sendForm(): void { 62 | if($this->currentSender instanceof Player) { 63 | $this->currentSender->sendForm(new CustomForm($this->plugin->getName(), [ 64 | new Label("description", $this->getDescription()), 65 | new Input("targetMember", "Target Member", "Target Member Name") 66 | ], 67 | function(Player $player, CustomFormResponse $response): void { 68 | $this->setCurrentSender($player); 69 | $this->onRun($player, $this->getName(), [ 70 | "targetMember" => $this->memberFactory->getMember($response->getString("targetMember")) 71 | ]); 72 | } 73 | )); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/CreateRoleCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Commando\args\RawStringArgument; 34 | use CortexPE\Commando\BaseCommand; 35 | use CortexPE\Hierarchy\command\HierarchySubCommand; 36 | use dktapps\pmforms\CustomForm; 37 | use dktapps\pmforms\CustomFormResponse; 38 | use dktapps\pmforms\element\Input; 39 | use dktapps\pmforms\element\Label; 40 | use pocketmine\command\CommandSender; 41 | use pocketmine\Player; 42 | use function count; 43 | 44 | class CreateRoleCommand extends HierarchySubCommand implements FormedCommand { 45 | protected function prepare(): void { 46 | $this->registerArgument(0, new RawStringArgument("roleName", true)); 47 | $this->setPermission("hierarchy;hierarchy.role;hierarchy.role.create"); 48 | } 49 | 50 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 51 | if($this->isSenderInGameNoArguments($args)) { 52 | $this->sendForm(); 53 | 54 | return; 55 | } elseif(count($args) < 1) { 56 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 57 | 58 | return; 59 | } 60 | 61 | $newRole = $this->roleManager->createRole($args["roleName"]); 62 | $this->sendFormattedMessage("cmd.createrole.success", [ 63 | "role" => $newRole->getName(), 64 | "role_id" => $newRole->getId(), 65 | ]); 66 | } 67 | 68 | public function sendForm(): void { 69 | if($this->currentSender instanceof Player) { 70 | $this->currentSender->sendForm(new CustomForm($this->plugin->getName(), [ 71 | new Label("description", $this->getDescription()), 72 | new Input("roleName", "Role", "New Role Name"), 73 | ], function (Player $player, CustomFormResponse $response): void { 74 | $this->setCurrentSender($player); 75 | $this->onRun($player, $this->getName(), [ 76 | "roleName" => $response->getString("roleName") 77 | ]); 78 | })); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/DeleteRoleCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Commando\BaseCommand; 34 | use CortexPE\Hierarchy\command\args\RoleArgument; 35 | use CortexPE\Hierarchy\command\HierarchySubCommand; 36 | use CortexPE\Hierarchy\role\Role; 37 | use dktapps\pmforms\CustomForm; 38 | use dktapps\pmforms\CustomFormResponse; 39 | use dktapps\pmforms\element\Dropdown; 40 | use dktapps\pmforms\element\Label; 41 | use pocketmine\command\CommandSender; 42 | use pocketmine\Player; 43 | use function count; 44 | 45 | class DeleteRoleCommand extends HierarchySubCommand implements FormedCommand { 46 | protected function prepare(): void { 47 | $this->registerArgument(0, new RoleArgument("role", true)); 48 | $this->setPermission("hierarchy;hierarchy.role;hierarchy.role.delete"); 49 | } 50 | 51 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 52 | if($this->isSenderInGameNoArguments($args)) { 53 | $this->sendForm(); 54 | 55 | return; 56 | } elseif(count($args) < 1) { 57 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 58 | 59 | return; 60 | } 61 | 62 | /** @var Role|null $role */ 63 | $role = $args["role"]; 64 | if($role instanceof Role) { 65 | $formats = [ 66 | "role" => $role->getName(), 67 | "role_id" => $role->getId(), 68 | ]; 69 | 70 | if(!$this->doHierarchyPositionCheck($role)) { 71 | return; 72 | } 73 | 74 | if(!$role->isDefault()) { 75 | $this->roleManager->deleteRole($role); 76 | $this->sendFormattedMessage("cmd.deleterole.success", $formats); 77 | } else { 78 | $this->sendFormattedMessage("cmd.deleterole.fail_role_default", $formats); 79 | } 80 | } else { 81 | $this->sendFormattedMessage("err.unknown_role"); 82 | } 83 | } 84 | 85 | public function sendForm(): void { 86 | if($this->currentSender instanceof Player) { 87 | if(!$this->getRolesApplicable($roles, $roles_i)) { 88 | return; 89 | } 90 | $this->currentSender->sendForm(new CustomForm($this->plugin->getName(), [ 91 | new Label("description", $this->getDescription()), 92 | new Dropdown("role", "Role", $roles), 93 | ], function (Player $player, CustomFormResponse $response) use ($roles_i): void { 94 | $this->setCurrentSender($player); 95 | $this->onRun($player, $this->getName(), [ 96 | "role" => $roles_i[$response->getInt("role")] 97 | ]); 98 | })); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/DenyPermCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | use pocketmine\permission\Permission; 36 | 37 | class DenyPermCommand extends ACPermissionModifierCommand { 38 | protected const CHILD_PERMISSION = "deny_permission"; 39 | protected const MESSAGE_ROOT = "denyperm"; 40 | 41 | protected function doOperationOnMember(BaseMember $member, Permission $permission): void { 42 | $member->denyMemberPermission($permission); 43 | } 44 | 45 | protected function doOperationOnRole(Role $role, Permission $permission): void { 46 | $role->denyPermission($permission); 47 | } 48 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/FlushCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Hierarchy\command\HierarchySubCommand; 34 | use pocketmine\command\CommandSender; 35 | 36 | class FlushCommand extends HierarchySubCommand { 37 | protected function prepare(): void { 38 | $this->setPermission("hierarchy;hierarchy.flush"); 39 | } 40 | 41 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 42 | $this->plugin->getRoleDataSource()->flush(); 43 | $this->plugin->getMemberDataSource()->flush(); 44 | $this->sendFormattedMessage("cmd.flush.success"); 45 | } 46 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/FormedCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | interface FormedCommand { 34 | public function sendForm(): void; 35 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/GiveRoleCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | 36 | class GiveRoleCommand extends ACMemberRoleModifierCommand { 37 | protected const CHILD_PERMISSION = "give"; 38 | protected const MESSAGE_ROOT = "giverole"; 39 | 40 | protected function doOperationOnMember(BaseMember $member, Role $role, bool $temporary, array $msgFormats): void { 41 | if(!$member->hasRole($role)) { 42 | $member->addRole($role, true, !$temporary); 43 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".success", $msgFormats); 44 | } else { 45 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".has_role", $msgFormats); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/GrantPermCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | use pocketmine\permission\Permission; 36 | 37 | class GrantPermCommand extends ACPermissionModifierCommand { 38 | protected const CHILD_PERMISSION = "add_permission"; 39 | protected const MESSAGE_ROOT = "grantperm"; 40 | 41 | protected function doOperationOnMember(BaseMember $member, Permission $permission): void { 42 | $member->addMemberPermission($permission); 43 | } 44 | 45 | protected function doOperationOnRole(Role $role, Permission $permission): void { 46 | $role->addPermission($permission); 47 | } 48 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/InfoCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Commando\BaseCommand; 34 | use CortexPE\Hierarchy\command\args\InfoTargetEnumArgument; 35 | use CortexPE\Hierarchy\command\args\MemberArgument; 36 | use CortexPE\Hierarchy\command\args\RoleArgument; 37 | use CortexPE\Hierarchy\command\HierarchySubCommand; 38 | use CortexPE\Hierarchy\Hierarchy; 39 | use CortexPE\Hierarchy\lang\MessageStore; 40 | use CortexPE\Hierarchy\member\BaseMember; 41 | use CortexPE\Hierarchy\role\Role; 42 | use dktapps\pmforms\CustomForm; 43 | use dktapps\pmforms\CustomFormResponse; 44 | use dktapps\pmforms\element\Dropdown; 45 | use dktapps\pmforms\element\Input; 46 | use dktapps\pmforms\element\Label; 47 | use dktapps\pmforms\MenuForm; 48 | use dktapps\pmforms\MenuOption; 49 | use pocketmine\command\CommandSender; 50 | use pocketmine\permission\PermissionManager; 51 | use pocketmine\Player; 52 | use pocketmine\utils\TextFormat; 53 | use function array_map; 54 | use function count; 55 | use function implode; 56 | 57 | class InfoCommand extends HierarchySubCommand implements FormedCommand { 58 | /** @var array */ 59 | private $opts = []; 60 | 61 | public function __construct(Hierarchy $plugin, string $name, string $description = "", array $aliases = []) { 62 | parent::__construct($plugin, $name, $description, $aliases); 63 | $roles = []; 64 | $roles_i = []; 65 | foreach($this->roleManager->getRoles() as $role) { 66 | $roles[] = "{$role->getName()} ({$role->getId()})"; 67 | $roles_i[] = $role->getId(); 68 | } 69 | $this->opts = [ 70 | [ 71 | new MenuOption("Member"), 72 | InfoTargetEnumArgument::TARGET_MEMBER, 73 | [ 74 | new Label("instruction", MessageStore::getMessage("cmd.info.member_form.instruction")), 75 | new Input("member", MessageStore::getMessage("cmd.info.member_form.opt_text")) 76 | ], 77 | function(Player $player, CustomFormResponse $response): void { 78 | $this->setCurrentSender($player); 79 | $this->onRun($player, $this->getName(), [ 80 | "targetType" => InfoTargetEnumArgument::TARGET_MEMBER, 81 | "targetMember" => [$this->memberFactory->getMember($response->getString("member"))] 82 | ]); 83 | } 84 | ], 85 | [ 86 | new MenuOption("Role"), 87 | InfoTargetEnumArgument::TARGET_ROLE, 88 | [ 89 | new Label("instruction", MessageStore::getMessage("cmd.info.role_form.instruction")), 90 | new Dropdown( 91 | "roles", 92 | MessageStore::getMessage("cmd.info.role_form.opt_text"), 93 | $roles 94 | ) 95 | ], 96 | function(Player $player, CustomFormResponse $response) use ($roles, $roles_i): void { 97 | $this->setCurrentSender($player); 98 | $this->onRun($player, $this->getName(), [ 99 | "targetType" => InfoTargetEnumArgument::TARGET_ROLE, 100 | "targetRole" => [$this->roleManager->getRole($roles_i[$response->getInt("roles")])] 101 | ]); 102 | } 103 | ], 104 | [ 105 | new MenuOption("Role List"), 106 | InfoTargetEnumArgument::TARGET_ROLE_LIST, 107 | null, 108 | null 109 | ], 110 | [ 111 | new MenuOption("Permission List"), 112 | InfoTargetEnumArgument::TARGET_PERM_LIST, 113 | null, 114 | null 115 | ], 116 | ]; 117 | } 118 | 119 | protected function prepare(): void { 120 | $this->registerArgument(0, new InfoTargetEnumArgument("targetType", true)); 121 | $this->registerArgument(1, new RoleArgument("targetRole", true)); 122 | $this->registerArgument(1, new MemberArgument("targetMember", true)); 123 | $this->setPermission(implode(";", [ 124 | "hierarchy", 125 | "hierarchy.info", 126 | "hierarchy.info.list_roles", 127 | "hierarchy.info.list_perms", 128 | "hierarchy.info.member", 129 | "hierarchy.info.role" 130 | ])); 131 | } 132 | 133 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 134 | if($this->isSenderInGameNoArguments($args)) { 135 | $this->sendForm(); 136 | 137 | return; 138 | } elseif(count($args) < 1) { 139 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 140 | 141 | return; 142 | } 143 | 144 | if($args["targetType"] === InfoTargetEnumArgument::TARGET_MEMBER && isset($args["targetMember"])) { 145 | if($sender->hasPermission("hierarchy.info.member")) { 146 | /** @var BaseMember $target */ 147 | $target = $args["targetMember"]; 148 | $this->sendFormattedMessage("cmd.info.member.header", [ 149 | "member" => $target->getName() 150 | ]); 151 | $this->sendFormattedMessage("cmd.info.member.roles_header"); 152 | foreach($target->getRoles() as $role) { 153 | $this->sendFormattedMessage("cmd.info.member.role_entry", [ 154 | "role" => $role->getName(), 155 | "role_id" => $role->getId() 156 | ]); 157 | } 158 | $this->sendFormattedMessage("cmd.info.member.m_perms_header"); 159 | if(count($target->getMemberPermissions()) > 0) { 160 | foreach($target->getMemberPermissions() as $permission => $allowed) { 161 | $this->sendFormattedMessage("cmd.info.member.m_perm_entry", [ 162 | "permission" => $permission, 163 | "color" => $allowed ? TextFormat::GREEN : TextFormat::RED . "-" 164 | ]); 165 | } 166 | } else { 167 | $this->sendFormattedMessage("cmd.info.member.no_extra_perms"); 168 | } 169 | } else { 170 | $this->sendPermissionError(); 171 | } 172 | } elseif($args["targetType"] === InfoTargetEnumArgument::TARGET_ROLE && isset($args["targetRole"])) { 173 | if($sender->hasPermission("hierarchy.info.role")) { 174 | /** @var Role $target */ 175 | $target = $args["targetRole"]; 176 | $lines = []; 177 | $lines[] = MessageStore::getMessage("cmd.info.role.header", [ 178 | "role" => $target->getName(), 179 | "role_id" => $target->getId() 180 | ]); 181 | $lines[] = MessageStore::getMessage("cmd.info.role.position", [ 182 | "position" => $target->getPosition() 183 | ]); 184 | $lines[] = MessageStore::getMessage("cmd.info.role.default", [ 185 | "isDefault" => $target->isDefault() ? TextFormat::GREEN . "YES" : TextFormat::RED . "NO" 186 | ]); 187 | $lines[] = MessageStore::getMessage("cmd.info.role.perms_header"); 188 | foreach($target->getCombinedPermissions() as $permission => $allowed) { 189 | $lines[] = MessageStore::getMessage("cmd.info.role.perm_entry", [ 190 | "permission" => $permission, 191 | "color" => $allowed ? TextFormat::GREEN : TextFormat::RED . "-" 192 | ]); 193 | } 194 | if(!$target->isDefault()) { 195 | $lines[] = MessageStore::getMessage("cmd.info.role.members_header", [ 196 | "count" => ($c = count($target->getOnlineMembers())) 197 | ]); 198 | if($c > 0) { 199 | foreach($target->getOnlineMembers() as $member) { 200 | $lines[] = MessageStore::getMessage("cmd.info.role.member_entry", [ 201 | "member" => $member->getName() 202 | ]); 203 | } 204 | } else { 205 | $lines[] = MessageStore::getMessage("cmd.info.role.no_online_members"); 206 | } 207 | $target->getOfflineMembers(function(array $members) use ($lines, $sender): void { 208 | $lines[] = MessageStore::getMessage("cmd.info.role.offline_members_header", [ 209 | "count" => ($c = count($members)) 210 | ]); 211 | if($c > 0) { 212 | foreach($members as $member) { 213 | $lines[] = MessageStore::getMessage("cmd.info.role.offline_member_entry", [ 214 | "member" => $member->getName() 215 | ]); 216 | } 217 | } else { 218 | $lines[] = MessageStore::getMessage("cmd.info.role.no_offline_members"); 219 | } 220 | foreach($lines as $line) { 221 | $sender->sendMessage($line); 222 | } 223 | }); 224 | } else { 225 | foreach($lines as $line) { 226 | $sender->sendMessage($line); 227 | } 228 | } 229 | } else { 230 | $this->sendPermissionError(); 231 | } 232 | } elseif($args["targetType"] === InfoTargetEnumArgument::TARGET_ROLE_LIST) { 233 | $list = $this->roleManager->getRoles(); 234 | $this->sendFormattedMessage("cmd.info.role_list.header", [ 235 | "count" => ($c = count($list)) 236 | ]); 237 | if($c > 0) { 238 | foreach($list as $role) { 239 | $this->sendFormattedMessage("cmd.info.role_list.entry", [ 240 | "role" => $role->getName(), 241 | "role_id" => $role->getId(), 242 | ]); 243 | } 244 | } else { 245 | $this->sendFormattedMessage("err.no_roles"); 246 | } 247 | } elseif($args["targetType"] === InfoTargetEnumArgument::TARGET_PERM_LIST) { 248 | $list = PermissionManager::getInstance()->getPermissions(); 249 | $this->sendFormattedMessage("cmd.info.perm_list.header", [ 250 | "count" => ($c = count($list)) 251 | ]); 252 | if($c > 0) { 253 | foreach($list as $perm) { 254 | $this->sendFormattedMessage("cmd.info.perm_list.entry", [ 255 | "permission" => $perm->getName(), 256 | ]); 257 | } 258 | } else { 259 | $this->sendFormattedMessage("err.no_roles"); 260 | } 261 | } else { 262 | $this->sendUsage(); 263 | } 264 | } 265 | 266 | public function sendForm(): void { 267 | if($this->currentSender instanceof Player) { 268 | $this->currentSender->sendForm( 269 | new MenuForm( 270 | $this->plugin->getName(), 271 | MessageStore::getMessage("cmd.info.menu_form.description"), 272 | array_map(function(array $opt): MenuOption { 273 | return $opt[0]; 274 | }, $this->opts), 275 | function(Player $player, int $selectedOption): void { 276 | if($this->opts[$selectedOption][2] !== null) { 277 | $player->sendForm( 278 | new CustomForm( 279 | $this->plugin->getName(), 280 | $this->opts[$selectedOption][2], 281 | $this->opts[$selectedOption][3] 282 | ) 283 | ); 284 | } else { 285 | $this->setCurrentSender($player); 286 | $this->onRun($player, $this->getName(), [ 287 | "targetType" => $this->opts[$selectedOption][1] 288 | ]); 289 | } 290 | } 291 | ) 292 | ); 293 | } 294 | } 295 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/RevokePermCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | use pocketmine\permission\Permission; 36 | 37 | class RevokePermCommand extends ACPermissionModifierCommand { 38 | protected const CHILD_PERMISSION = "remove_permission"; 39 | protected const MESSAGE_ROOT = "revokeperm"; 40 | 41 | protected function doOperationOnMember(BaseMember $member, Permission $permission): void { 42 | $member->removeMemberPermission($permission); 43 | } 44 | 45 | protected function doOperationOnRole(Role $role, Permission $permission): void { 46 | $role->removePermission($permission); 47 | } 48 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/TakeRoleCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | 36 | class TakeRoleCommand extends ACMemberRoleModifierCommand { 37 | protected const CHILD_PERMISSION = "take"; 38 | protected const MESSAGE_ROOT = "takerole"; 39 | 40 | protected function doOperationOnMember(BaseMember $member, Role $role, bool $temporary, array $msgFormats): void { 41 | if($member->hasRole($role)) { 42 | $member->removeRole($role, true, !$temporary); 43 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".success", $msgFormats); 44 | } else { 45 | $this->sendFormattedMessage("cmd." . static::MESSAGE_ROOT . ".no_role", $msgFormats); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/command/subcommand/TransferPrivilegesCommand.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\command\subcommand; 31 | 32 | 33 | use CortexPE\Commando\BaseCommand; 34 | use CortexPE\Hierarchy\command\args\MemberArgument; 35 | use CortexPE\Hierarchy\command\HierarchySubCommand; 36 | use CortexPE\Hierarchy\data\member\MemberDataSource; 37 | use CortexPE\Hierarchy\member\BaseMember; 38 | use dktapps\pmforms\CustomForm; 39 | use dktapps\pmforms\CustomFormResponse; 40 | use dktapps\pmforms\element\Input; 41 | use dktapps\pmforms\element\Label; 42 | use pocketmine\command\CommandSender; 43 | use pocketmine\permission\Permission; 44 | use pocketmine\permission\PermissionManager; 45 | use pocketmine\Player; 46 | 47 | class TransferPrivilegesCommand extends HierarchySubCommand implements FormedCommand { 48 | 49 | protected function prepare(): void { 50 | $this->registerArgument(0, new MemberArgument("sourceMember", false)); 51 | $this->registerArgument(1, new MemberArgument("targetMember", false)); 52 | $this->setPermission(implode(";", [ 53 | "hierarchy", 54 | "hierarchy.member", 55 | "hierarchy.member.transfer_privileges" 56 | ])); 57 | } 58 | 59 | public function onRun(CommandSender $sender, string $aliasUsed, array $args): void { 60 | if($this->isSenderInGameNoArguments($args)) { 61 | $this->sendForm(); 62 | 63 | return; 64 | } elseif(count($args) < 2) { 65 | $this->sendError(BaseCommand::ERR_INSUFFICIENT_ARGUMENTS); 66 | 67 | return; 68 | } 69 | 70 | /** @var BaseMember $source */ 71 | $source = $args["sourceMember"]; 72 | /** @var BaseMember $target */ 73 | $target = $args["targetMember"]; 74 | 75 | if($source === $target) { 76 | $this->sendFormattedMessage("cmd.transfer_privileges.same_member"); 77 | return; 78 | } 79 | 80 | if(!$this->doHierarchyPositionCheck($source) || !$this->doHierarchyPositionCheck($target)) { 81 | return; 82 | } 83 | 84 | foreach($source->getRoles() as $role) { 85 | if($role->isDefault())continue; 86 | $target->addRole($role, true, false); 87 | $source->removeRole($role, true, false); 88 | } 89 | $pMgr = PermissionManager::getInstance(); 90 | foreach($source->getMemberPermissions() as $permissionName => $value) { 91 | $perm = $pMgr->getPermission($permissionName); 92 | if($perm instanceof Permission) { 93 | if($value) { 94 | $target->addMemberPermission($perm, true, false); 95 | } else { 96 | $target->denyMemberPermission($perm, true, false); 97 | } 98 | } 99 | $source->removeMemberPermission($permissionName, true, false); 100 | } 101 | $this->plugin->getMemberDataSource()->updateMemberData($source, MemberDataSource::ACTION_MEMBER_TRANSFER_DATA, ["target" => $target->getName()]); 102 | $this->sendFormattedMessage("cmd.transfer_privileges.success", [ 103 | "source" => $source->getName(), 104 | "target" => $target->getName() 105 | ]); 106 | } 107 | 108 | public function sendForm(): void { 109 | if($this->currentSender instanceof Player) { 110 | $this->currentSender->sendForm(new CustomForm($this->plugin->getName(), [ 111 | new Label("description", $this->getDescription()), 112 | new Input("sourceMember", "Source Member", "Source Member Name"), 113 | new Input("targetMember", "Target Member", "Target Member Name") 114 | ], 115 | function(Player $player, CustomFormResponse $response): void { 116 | $this->setCurrentSender($player); 117 | $this->onRun($player, $this->getName(), [ 118 | "sourceMember" => $this->memberFactory->getMember($response->getString("sourceMember")), 119 | "targetMember" => $this->memberFactory->getMember($response->getString("targetMember")) 120 | ]); 121 | } 122 | )); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/DataSource.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | 35 | /** 36 | * Class DataSource 37 | * @package CortexPE\Hierarchy\data 38 | * @internal This class (and its children) are only used for the plugin's internal data storage. DO NOT TOUCH! 39 | */ 40 | abstract class DataSource { 41 | /** @var Hierarchy */ 42 | protected $plugin; 43 | 44 | public function __construct(Hierarchy $plugin) { 45 | $this->plugin = $plugin; 46 | } 47 | 48 | abstract public function initialize(): void; 49 | 50 | protected function postInitialize(array $roles): void { 51 | $this->plugin->getRoleManager()->loadRoles($roles); 52 | $this->plugin->continueStartup(); 53 | } 54 | 55 | /** 56 | * @return Hierarchy 57 | */ 58 | public function getPlugin(): Hierarchy { 59 | return $this->plugin; 60 | } 61 | 62 | /** 63 | * Gracefully shutdown the data source 64 | */ 65 | abstract public function shutdown(): void; 66 | 67 | /** 68 | * Save current state to disk (if applicable) 69 | */ 70 | abstract public function flush(): void; 71 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/IndexedLDR.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | use DirectoryIterator; 33 | use Generator; 34 | use pocketmine\permission\Permission; 35 | use pocketmine\permission\PermissionManager; 36 | use function array_map; 37 | use function array_merge; 38 | use function array_values; 39 | use function file_exists; 40 | use function file_get_contents; 41 | 42 | abstract class IndexedLDR extends LegacyDataReader { 43 | /** @var string */ 44 | protected const FILE_EXTENSION = null; 45 | 46 | abstract function decode(string $string): array; 47 | 48 | public function getMemberDatum(): Generator { 49 | foreach(new DirectoryIterator($this->plugin->getDataFolder() . "members") as $fInfo) { 50 | if($fInfo->getExtension() === self::FILE_EXTENSION) { 51 | $dat = $this->decode(file_get_contents($fInfo->getPathname())); 52 | 53 | yield [ 54 | "name" => $fInfo->getBasename("." . $fInfo->getExtension()), // strip off extension 55 | "roles" => array_merge([$this->getDefaultRoleID()], $dat["roles"] ?? []), 56 | "permissions" => $dat["permissions"] ?? [] 57 | ]; 58 | } 59 | } 60 | } 61 | 62 | public function shutdown(): void { 63 | // noop 64 | } 65 | 66 | public function getRoles(): array { 67 | if(file_exists(($fp = $this->plugin->getDataFolder() . "roles." . static::FILE_EXTENSION))) { 68 | return $this->decode(file_get_contents($fp)); 69 | } else { 70 | $pMgr = PermissionManager::getInstance(); 71 | 72 | return [ 73 | [ 74 | "ID" => 1, 75 | "Position" => 0, 76 | "Name" => "Member", 77 | "isDefault" => true, 78 | "Permissions" => array_map(function (Permission $perm) { 79 | return $perm->getName(); 80 | }, array_values($pMgr->getDefaultPermissions(false))) 81 | ], 82 | [ 83 | "ID" => 2, 84 | "Position" => 1, 85 | "Name" => "Operator", 86 | "isDefault" => false, 87 | "Permissions" => array_map(function (Permission $perm) { 88 | return $perm->getName(); 89 | }, array_values($pMgr->getDefaultPermissions(true))) 90 | ], 91 | ]; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/JSONLDR.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | 33 | class JSONLDR extends IndexedLDR { 34 | /** @var string */ 35 | protected const FILE_EXTENSION = "json"; 36 | 37 | public function decode(string $string): array { 38 | return json_decode($string, true); 39 | } 40 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/LegacyDataReader.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use Generator; 35 | 36 | abstract class LegacyDataReader { 37 | /** @var Hierarchy */ 38 | protected $plugin; 39 | 40 | public function __construct(Hierarchy $plugin) { 41 | $this->plugin = $plugin; 42 | } 43 | 44 | /** 45 | * Gracefully shutdown the legacy data reader 46 | */ 47 | abstract public function shutdown(): void; 48 | 49 | abstract public function getRoles(): array; 50 | 51 | abstract public function getMemberDatum(): Generator; 52 | 53 | protected function getDefaultRoleID(): ?int { 54 | foreach($this->getRoles() as $role) { 55 | if((bool)$role["isDefault"]) { 56 | return $role["ID"]; 57 | } 58 | } 59 | 60 | return null; 61 | } 62 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/MySQLLDR.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | 35 | class MySQLLDR extends SQLLDR { 36 | protected const DIALECT = "mysql"; 37 | protected const STMTS_FILE = "mysql_stmts.sql"; 38 | 39 | public function getExtraDBSettings(Hierarchy $plugin, array $config): array { 40 | $config["schema"] = $config["schema"] ?? strtolower($plugin->getName()); 41 | 42 | return $config; 43 | } 44 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/SQLLDR.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use Generator; 35 | use pocketmine\permission\Permission; 36 | use pocketmine\permission\PermissionManager; 37 | use poggit\libasynql\DataConnector; 38 | use poggit\libasynql\libasynql; 39 | use SOFe\AwaitGenerator\Await; 40 | use Throwable; 41 | use function array_map; 42 | use function array_values; 43 | 44 | abstract class SQLLDR extends LegacyDataReader { 45 | protected const STMTS_FILE = null; 46 | protected const DIALECT = null; 47 | 48 | /** @var DataConnector */ 49 | protected $db; 50 | 51 | /** @var array */ 52 | protected $roles = []; 53 | /** @var array */ 54 | private $memberData = []; // I can't think of any way to yield back data from inside an anonymous function 55 | 56 | public function __construct(Hierarchy $plugin, array $config) { 57 | parent::__construct($plugin); 58 | 59 | $this->db = libasynql::create($plugin, [ 60 | "type" => static::DIALECT, 61 | static::DIALECT => $this->getExtraDBSettings($plugin, $config), 62 | "worker-limit" => $config["workerLimit"] 63 | ], [ 64 | static::DIALECT => static::STMTS_FILE 65 | ]); 66 | 67 | Await::f2c(function () { 68 | $this->roles = yield $this->asyncSelect("hierarchy.role.list"); 69 | if(empty($this->roles)) { 70 | $pMgr = PermissionManager::getInstance(); 71 | $listPerms = function (Permission $perm):string{ 72 | return $perm->getName(); 73 | }; 74 | $this->roles[] = [ 75 | "ID" => 1, 76 | "Position" => 0, 77 | "Name" => "Member", 78 | "isDefault" => 1, 79 | "Permissions" => array_values(array_map($listPerms, $pMgr->getDefaultPermissions(false))) 80 | ]; 81 | $this->roles[] = [ 82 | "ID" => 2, 83 | "Position" => 1, 84 | "Name" => "Operator", 85 | "isDefault" => 0, 86 | "Permissions" => array_values(array_map($listPerms, $pMgr->getDefaultPermissions(true))) 87 | ]; 88 | } else { 89 | foreach($this->roles as $k => $role) { 90 | $permissions = yield $this->asyncSelect("hierarchy.role.permissions.get", [ 91 | "role_id" => $role["ID"] 92 | ]); 93 | foreach($permissions as $permission_row) { 94 | $this->roles[$k]["Permissions"][] = $permission_row["Permission"]; 95 | } 96 | } 97 | } 98 | $memberNames = yield $this->asyncSelect("hierarchy.member.list"); 99 | foreach($memberNames as $name){ 100 | $name = $name["Player"]; 101 | $this->memberData[$name] = [ 102 | "name" => $name, 103 | "roles" => [$this->getDefaultRoleID()] 104 | ]; 105 | $rows = yield $this->asyncSelect("hierarchy.member.roles.get", [ 106 | "username" => $name 107 | ]); 108 | foreach($rows as $row) { 109 | $this->memberData[$name]["roles"][] = $row["RoleID"]; 110 | } 111 | $rows = yield $this->asyncSelect("hierarchy.member.permissions.get", [ 112 | "username" => $name 113 | ]); 114 | foreach($rows as $row) { 115 | $this->memberData[$name]["permissions"][] = $row["Permission"]; 116 | } 117 | } 118 | }, function () { 119 | }, function (Throwable $err) { 120 | $this->plugin->getLogger()->logException($err); 121 | }); 122 | $this->db->waitAll(); 123 | } 124 | 125 | /** 126 | * @return array 127 | */ 128 | public function getRoles(): array { 129 | return $this->roles; 130 | } 131 | 132 | abstract function getExtraDBSettings(Hierarchy $plugin, array $config): array; 133 | 134 | protected function asyncSelect(string $query, array $args = []): Generator { 135 | $this->db->executeSelect($query, $args, yield, yield Await::REJECT); 136 | 137 | return yield Await::ONCE; 138 | } 139 | 140 | public function getMemberDatum(): Generator { 141 | foreach($this->memberData as $datum){ 142 | yield $datum; 143 | } 144 | } 145 | 146 | public function shutdown(): void { 147 | if($this->db instanceof DataConnector) { 148 | $this->db->waitAll(); 149 | $this->db->close(); 150 | } 151 | } 152 | 153 | /** 154 | * @return DataConnector 155 | */ 156 | public function getDB(): DataConnector { 157 | return $this->db; 158 | } 159 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/SQLiteLDR.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | 35 | class SQLiteLDR extends SQLLDR { 36 | protected const DIALECT = "sqlite"; 37 | protected const STMTS_FILE = "sqlite_stmts.sql"; 38 | 39 | public function getExtraDBSettings(Hierarchy $plugin, array $config): array { 40 | return [ 41 | "file" => $plugin->getDataFolder() . $config["dbPath"] 42 | ]; 43 | } 44 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/legacy/YAMLLDR.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\legacy; 31 | 32 | 33 | class YAMLLDR extends IndexedLDR { 34 | /** @var string */ 35 | protected const FILE_EXTENSION = "yml"; 36 | 37 | public function decode(string $string): array { 38 | return yaml_parse($string); 39 | } 40 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/member/MemberDataSource.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\member; 31 | 32 | 33 | use CortexPE\Hierarchy\data\DataSource; 34 | use CortexPE\Hierarchy\member\BaseMember; 35 | use CortexPE\Hierarchy\role\Role; 36 | 37 | abstract class MemberDataSource extends DataSource { 38 | public const ACTION_MEMBER_TRANSFER_DATA = "member.transfer_data"; 39 | public const ACTION_MEMBER_ROLE_ADD = "member.role.add"; 40 | public const ACTION_MEMBER_ROLE_REMOVE = "member.role.remove"; 41 | public const ACTION_MEMBER_ROLE_REMOVE_ALL = "member.role.remove_all"; 42 | public const ACTION_MEMBER_PERMS_ADD = "member.perm.add"; 43 | public const ACTION_MEMBER_PERMS_REMOVE = "member.perm.remove"; 44 | public const ACTION_MEMBER_PERMS_REMOVE_ALL = "member.perm.remove_all"; 45 | public const ACTION_MEMBER_UPDATE_ROLE_ETC = "member.etc.update.role"; 46 | public const ACTION_MEMBER_UPDATE_PERMISSION_ETC = "member.etc.update.permission"; 47 | 48 | /** 49 | * @param BaseMember $member 50 | * 51 | * @internal Get member data from the data source then pass to member object 52 | * 53 | */ 54 | abstract public function loadMemberData(BaseMember $member): void; 55 | 56 | /** 57 | * @param BaseMember $member 58 | * @param string $action 59 | * @param mixed $data 60 | * 61 | * @internal Update member data on data source 62 | * 63 | */ 64 | abstract public function updateMemberData(BaseMember $member, string $action, $data = null): void; 65 | 66 | abstract public function getMemberNamesOf(Role $role): \Generator; 67 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/member/MySQLMemberDS.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\member; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use function strtolower; 35 | 36 | class MySQLMemberDS extends SQLMemberDS { 37 | protected const DIALECT = "mysql"; 38 | protected const STMTS_FILE = "mysql_stmts.sql"; 39 | 40 | public function getExtraDBSettings(Hierarchy $plugin, array $config): array { 41 | if(isset($config["user"]) && !isset($config["username"])){ 42 | $config["username"] = $config["user"]; 43 | } 44 | $config["schema"] = $config["schema"] ?? strtolower($plugin->getName()); 45 | 46 | return $config; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/member/SQLMemberDS.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\member; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use CortexPE\Hierarchy\member\BaseMember; 35 | use CortexPE\Hierarchy\role\Role; 36 | use Generator; 37 | use poggit\libasynql\DataConnector; 38 | use poggit\libasynql\libasynql; 39 | use SOFe\AwaitGenerator\Await; 40 | use Throwable; 41 | 42 | abstract class SQLMemberDS extends MemberDataSource { 43 | protected const STMTS_FILE = null; 44 | protected const DIALECT = null; 45 | /** @var DataConnector */ 46 | protected $db; 47 | 48 | public function __construct(Hierarchy $plugin, array $config) { 49 | parent::__construct($plugin); 50 | 51 | $this->db = libasynql::create($plugin, [ 52 | "type" => static::DIALECT, 53 | static::DIALECT => $this->getExtraDBSettings($plugin, $config), 54 | "worker-limit" => $config["workerLimit"] 55 | ], [ 56 | static::DIALECT => static::STMTS_FILE 57 | ]); 58 | } 59 | 60 | public function initialize(): void { 61 | Await::f2c(function () { 62 | foreach( 63 | [ 64 | "hierarchy.init.memberRolesTable", 65 | "hierarchy.init.memberPermissionsTable", 66 | ] as $tableSchema 67 | ){ 68 | yield $this->asyncGenericQuery($tableSchema); 69 | } 70 | foreach( 71 | [ 72 | "hierarchy.check.memberRoles_check1" => "hierarchy.migrate.memberRoles_patch1", 73 | "hierarchy.check.memberPermissions_check1" => "hierarchy.migrate.memberPermissions_patch1", 74 | ] as $checkQuery => $failedQuery 75 | ){ 76 | if(!(bool)(yield $this->asyncSelect($checkQuery))[0]["result"]) { 77 | yield $this->asyncGenericQuery($failedQuery); 78 | } 79 | } 80 | }, function () { 81 | }, function (Throwable $err) { 82 | $this->getPlugin()->getLogger()->logException($err); 83 | }); 84 | $this->db->waitAll(); 85 | } 86 | 87 | abstract function getExtraDBSettings(Hierarchy $plugin, array $config): array; 88 | 89 | protected function asyncGenericQuery(string $query, array $args = []): Generator { 90 | $this->db->executeGeneric($query, $args, yield, yield Await::REJECT); 91 | 92 | return yield Await::ONCE; 93 | } 94 | 95 | protected function asyncSelect(string $query, array $args = []): Generator { 96 | $this->db->executeSelect($query, $args, yield, yield Await::REJECT); 97 | 98 | return yield Await::ONCE; 99 | } 100 | 101 | protected function asyncInsert(string $query, array $args = []): Generator { 102 | $this->db->executeInsert($query, $args, yield, yield Await::REJECT); 103 | 104 | return yield Await::ONCE; 105 | } 106 | 107 | public function loadMemberData(BaseMember $member): void { 108 | Await::f2c(function () use ($member) { 109 | $data = [ 110 | "roles" => [ 111 | $this->plugin->getRoleManager()->getDefaultRole()->getId() => null 112 | ] 113 | ]; 114 | $rows = yield $this->asyncSelect("hierarchy.member.roles.get", [ 115 | "username" => $member->getName() 116 | ]); 117 | foreach($rows as $row){ 118 | $data["roles"][$row["RoleID"]] = $row["AdditionalData"] !== null ? json_decode($row["AdditionalData"], true) : null; 119 | } 120 | $rows = yield $this->asyncSelect("hierarchy.member.permissions.get", [ 121 | "username" => $member->getName() 122 | ]); 123 | foreach($rows as $row){ 124 | $data["permissions"][$row["Permission"]] = $row["AdditionalData"] !== null ? json_decode($row["AdditionalData"], true) : null; 125 | } 126 | $member->loadData($data); 127 | }, null, function (Throwable $err) { 128 | $this->getPlugin()->getLogger()->logException($err); 129 | }); 130 | } 131 | 132 | public function updateMemberData(BaseMember $member, string $action, $data = null): void { 133 | switch($action) { 134 | case self::ACTION_MEMBER_ROLE_ADD: 135 | $this->db->executeChange("hierarchy.member.roles.add", [ 136 | "username" => $member->getName(), 137 | "role_id" => (int)$data 138 | ]); 139 | break; 140 | case self::ACTION_MEMBER_ROLE_REMOVE: 141 | $this->db->executeChange("hierarchy.member.roles.remove", [ 142 | "username" => $member->getName(), 143 | "role_id" => (int)$data 144 | ]); 145 | break; 146 | case self::ACTION_MEMBER_PERMS_ADD: 147 | $this->db->executeChange("hierarchy.member.permissions.add", [ 148 | "username" => $member->getName(), 149 | "permission" => $data 150 | ]); 151 | break; 152 | case self::ACTION_MEMBER_PERMS_REMOVE: 153 | $this->db->executeChange("hierarchy.member.permissions.remove", [ 154 | "username" => $member->getName(), 155 | "permission" => $data 156 | ]); 157 | break; 158 | case self::ACTION_MEMBER_UPDATE_ROLE_ETC: 159 | $this->db->executeChange("hierarchy.member.etc.update.role", [ 160 | "username" => $member->getName(), 161 | "role_id" => $data[0], 162 | "additional_data" => json_encode($data[1]) 163 | ]); 164 | break; 165 | case self::ACTION_MEMBER_UPDATE_PERMISSION_ETC: 166 | $this->db->executeChange("hierarchy.member.etc.update.permission", [ 167 | "username" => $member->getName(), 168 | "permission" => $data[0], 169 | "additional_data" => json_encode($data[1]) 170 | ]); 171 | break; 172 | case self::ACTION_MEMBER_TRANSFER_DATA: 173 | $this->db->executeChange("hierarchy.member.roles.transfer", [ 174 | "source" => $member->getName(), 175 | "target" => $data["target"], 176 | ]); 177 | $this->db->executeChange("hierarchy.member.permissions.transfer", [ 178 | "source" => $member->getName(), 179 | "target" => $data["target"], 180 | ]); 181 | break; 182 | case self::ACTION_MEMBER_PERMS_REMOVE_ALL: 183 | case self::ACTION_MEMBER_ROLE_REMOVE_ALL: 184 | $this->db->executeGeneric("hierarchy.member.roles.remove_all", [ 185 | "username" => $member->getName(), 186 | ]); 187 | $this->db->executeGeneric("hierarchy.member.permissions.remove_all", [ 188 | "username" => $member->getName(), 189 | ]); 190 | break; 191 | } 192 | } 193 | 194 | public function getMemberNamesOf(Role $role): \Generator { 195 | return $this->asyncSelect("hierarchy.role.members", [ 196 | "role_id" => $role->getId() 197 | ]); 198 | } 199 | 200 | public function shutdown(): void { 201 | if($this->db instanceof DataConnector) { 202 | $this->db->waitAll(); 203 | $this->db->close(); 204 | } 205 | } 206 | 207 | public function flush(): void { 208 | // noop 209 | } 210 | 211 | /** 212 | * @return DataConnector 213 | */ 214 | public function getDB(): DataConnector { 215 | return $this->db; 216 | } 217 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/member/SQLiteMemberDS.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\member; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | 35 | class SQLiteMemberDS extends SQLMemberDS { 36 | protected const DIALECT = "sqlite"; 37 | protected const STMTS_FILE = "sqlite_stmts.sql"; 38 | 39 | public function getExtraDBSettings(Hierarchy $plugin, array $config): array { 40 | return [ 41 | "file" => $plugin->getDataFolder() . $config["dbPath"] 42 | ]; 43 | } 44 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/migrator/BaseMigrator.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\migrator; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use RecursiveDirectoryIterator; 35 | use RecursiveIteratorIterator; 36 | 37 | abstract class BaseMigrator { 38 | private final function __construct() { 39 | } 40 | 41 | abstract public static function tryMigration(Hierarchy $plugin): void; 42 | 43 | protected static function createBackup(Hierarchy $plugin):void { 44 | self::recursiveCopy( 45 | $plugin->getDataFolder(), 46 | realpath($plugin->getDataFolder()) . "_backup_" . date("d-m-Y_H-i-s") 47 | ); 48 | } 49 | 50 | /** 51 | * @param string $path 52 | * @param string $destination 53 | */ 54 | private static function recursiveCopy(string $path, string $destination): void { 55 | mkdir($destination); 56 | /** @var \SplFileInfo $item */ 57 | foreach( 58 | $iterator = new RecursiveIteratorIterator( 59 | new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), 60 | RecursiveIteratorIterator::SELF_FIRST) as $item 61 | ) { 62 | if($item->isDir()) { 63 | mkdir($destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName()); 64 | } else { 65 | copy($item->getPathname(), $destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName()); 66 | } 67 | } 68 | } 69 | public static function iterateDirectoryFiles(string $directoryPath):\Generator{ 70 | if ($handle = opendir($directoryPath)) { 71 | while (false !== ($file = readdir($handle))) { 72 | if(in_array($file, [".", ".."])){ 73 | continue; 74 | } 75 | 76 | yield $directoryPath . DIRECTORY_SEPARATOR . $file; 77 | } 78 | closedir($handle); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/migrator/DSMigrator.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\migrator; 31 | 32 | 33 | use CortexPE\Hierarchy\data\legacy\IndexedLDR; 34 | use CortexPE\Hierarchy\data\legacy\JSONLDR; 35 | use CortexPE\Hierarchy\data\legacy\MySQLLDR; 36 | use CortexPE\Hierarchy\data\legacy\SQLiteLDR; 37 | use CortexPE\Hierarchy\data\legacy\SQLLDR; 38 | use CortexPE\Hierarchy\data\legacy\YAMLLDR; 39 | use CortexPE\Hierarchy\data\role\YAMLRoleDS; 40 | use CortexPE\Hierarchy\exception\StartupFailureException; 41 | use CortexPE\Hierarchy\Hierarchy; 42 | use function file_exists; 43 | use function unlink; 44 | use function yaml_emit_file; 45 | use function yaml_parse_file; 46 | 47 | class DSMigrator extends BaseMigrator { 48 | public static function tryMigration(Hierarchy $plugin): void { 49 | if(file_exists(($fp = $plugin->getDataFolder() . "config.yml"))) { 50 | $data = yaml_parse_file($fp); 51 | if(!isset($data["configVersion"])) { // v1.0.0 -> v1.1.0 52 | $plugin->getLogger()->info("Migrating all data to newer Data Storage format"); 53 | self::createBackup($plugin); 54 | unlink($fp); 55 | $plugin->saveConfig(); 56 | $conf = $plugin->getConfig(); 57 | switch($data["dataSource"]["type"]) { 58 | case "json": 59 | $source = new JSONLDR($plugin); 60 | break; 61 | case "yaml": 62 | $source = new YAMLLDR($plugin); 63 | break; 64 | case "mysql": 65 | $source = new MySQLLDR($plugin, $data["dataSource"]["mysql"]); 66 | break; 67 | case "sqlite3": 68 | $source = new SQLiteLDR($plugin, $data["dataSource"]["sqlite3"]); 69 | break; 70 | default: 71 | unlink($fp); // delete new updated config 72 | yaml_emit_file($fp, $data); // restore old 73 | throw new StartupFailureException("Invalid legacy configuration file"); 74 | } 75 | $conf->setNested("memberDataSource.type", $data["dataSource"]["type"]); 76 | $conf->setNested("memberDataSource.sqlite3", $data["dataSource"]["sqlite3"]); 77 | $conf->setNested("memberDataSource.mysql", $data["dataSource"]["mysql"]); 78 | if(!($source instanceof IndexedLDR)) { 79 | // default to yaml 80 | $conf->setNested("roleDataSource.type", "yaml"); 81 | 82 | // this will only run when it's roles from mysql / sqlite -> yaml anyway 83 | $target = new YAMLRoleDS($plugin); 84 | $target->setRoles($source->getRoles()); 85 | $target->flush(); 86 | $target->shutdown(); 87 | } 88 | if($source instanceof SQLLDR){ 89 | $db = $source->getDB(); 90 | $db->executeGeneric("hierarchy.drop.rolesTable"); 91 | $db->executeGeneric("hierarchy.drop.rolePermissionTable"); 92 | $db->waitAll(); 93 | } 94 | 95 | $conf->save(); 96 | $source->shutdown(); 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/migrator/IndexedToSQL.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\migrator; 31 | 32 | 33 | use CortexPE\Hierarchy\data\member\MemberDataSource; 34 | use CortexPE\Hierarchy\data\member\SQLiteMemberDS; 35 | use CortexPE\Hierarchy\Hierarchy; 36 | use CortexPE\Hierarchy\member\OfflineMember; 37 | 38 | class IndexedToSQL extends BaseMigrator { 39 | public static function tryMigration(Hierarchy $plugin): void { 40 | if(is_dir(($dir = $plugin->getDataFolder() . "members/"))){ 41 | $plugin->getLogger()->info("Migrating member data to newer Data Storage format"); 42 | self::createBackup($plugin); 43 | $target = new SQLiteMemberDS($plugin, $plugin->getConfig()->getNested("memberDataSource.sqlite3")); 44 | foreach(self::iterateDirectoryFiles($dir) as $file){ 45 | if(!is_dir($file) && in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ["json", "yml"])){ 46 | $data = yaml_parse(file_get_contents($file)); // yaml accepts json too 47 | $member = new OfflineMember($plugin, pathinfo($file, PATHINFO_FILENAME)); 48 | foreach($data["roles"] ?? [] as $roleID){ 49 | $target->updateMemberData($member, MemberDataSource::ACTION_MEMBER_ROLE_ADD, $roleID); 50 | } 51 | foreach($data["permissions"] ?? [] as $permission){ 52 | $target->updateMemberData($member, MemberDataSource::ACTION_MEMBER_PERMS_ADD, $permission); 53 | } 54 | unlink($file); 55 | } 56 | } 57 | $target->getDB()->waitAll(); 58 | $target->shutdown(); 59 | rmdir($dir); 60 | 61 | $plugin->getConfig()->setNested("memberDataSource.type", "sqlite3"); 62 | $plugin->getConfig()->set("configVersion", "1.6"); 63 | $plugin->getConfig()->save(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/migrator/RolePositionSimplifier.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\migrator; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | 35 | class RolePositionSimplifier extends BaseMigrator { 36 | public static function tryMigration(Hierarchy $plugin): void { 37 | switch($plugin->getConfig()->getNested("roleDataSource.type")){ 38 | case "yaml": 39 | $data = file_exists($fn = $plugin->getDataFolder() . "roles.yml") ? yaml_parse_file($fn) : []; 40 | break; 41 | case "json": 42 | $data = file_exists($fn = $plugin->getDataFolder() . "roles.json") ? json_decode(file_get_contents($fn), true) : []; 43 | break; 44 | default: 45 | throw new \InvalidStateException("Invalid role DataSource type, please use either 'json' or 'yaml'"); 46 | } 47 | $migrated = false; 48 | $roles = []; 49 | foreach($data as $n => $role){ 50 | if(isset($role["Position"])){ 51 | $n = $role["Position"]; 52 | unset($role["Position"]); 53 | if(isset($roles[$n])){ 54 | array_splice($roles, $n, 0, $role); 55 | } else { 56 | $roles[$n] = $role; 57 | } 58 | $migrated = true; 59 | } else { 60 | $roles[$n] = $role; 61 | } 62 | } 63 | if(!$migrated){ 64 | return; 65 | } 66 | $plugin->getLogger()->info("Migrating role configuration to new format"); 67 | self::createBackup($plugin); 68 | ksort($roles); 69 | $roles = array_values($roles); // remove keys 70 | switch($plugin->getConfig()->getNested("roleDataSource.type")){ 71 | case "yaml": 72 | file_put_contents($fn, yaml_emit($roles)); 73 | break; 74 | case "json": 75 | file_put_contents($fn, json_encode($roles, JSON_PRETTY_PRINT)); 76 | break; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/role/IndexedRoleDS.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\role; 31 | 32 | 33 | use CortexPE\Hierarchy\data\traits\IndexedDataUtilities; 34 | use CortexPE\Hierarchy\exception\UnresolvedRoleException; 35 | use CortexPE\Hierarchy\Hierarchy; 36 | use CortexPE\Hierarchy\role\Role; 37 | use pocketmine\permission\Permission; 38 | use pocketmine\permission\PermissionManager; 39 | use function array_map; 40 | use function array_values; 41 | use function file_exists; 42 | use function file_get_contents; 43 | use function file_put_contents; 44 | 45 | abstract class IndexedRoleDS extends RoleDataSource { 46 | use IndexedDataUtilities; 47 | 48 | /** @var string */ 49 | protected const FILE_EXTENSION = null; 50 | 51 | /** @var array */ 52 | protected $roles = []; 53 | 54 | /** @var string */ 55 | protected $rolesFile; 56 | 57 | public function __construct(Hierarchy $plugin) { 58 | parent::__construct($plugin); 59 | $this->rolesFile = $this->plugin->getDataFolder() . "roles." . static::FILE_EXTENSION; 60 | } 61 | 62 | public function initialize(): void { 63 | if(file_exists($this->rolesFile)) { 64 | $this->roles = $this->decode(file_get_contents($this->rolesFile)); 65 | } else { 66 | // create default role & add default permissions 67 | $pMgr = PermissionManager::getInstance(); 68 | file_put_contents($this->rolesFile, $this->encode(($this->roles = [ 69 | [ 70 | "ID" => 1, 71 | "Name" => "Member", 72 | "isDefault" => true, 73 | "Permissions" => array_map(function (Permission $perm) { 74 | return $perm->getName(); 75 | }, array_values($pMgr->getDefaultPermissions(false))) 76 | ], 77 | [ 78 | "ID" => 2, 79 | "Name" => "Operator", 80 | "isDefault" => false, 81 | "Permissions" => array_map(function (Permission $perm) { 82 | return $perm->getName(); 83 | }, array_values($pMgr->getDefaultPermissions(true))) 84 | ], 85 | ]))); 86 | } 87 | 88 | $this->postInitialize($this->roles); 89 | } 90 | 91 | abstract function decode(string $string): array; 92 | 93 | abstract function encode(array $data): string; 94 | 95 | public function addRolePermission(Role $role, Permission $permission, bool $inverted = false): void { 96 | $this->removeRolePermission($role, $permission); 97 | $permission = ($inverted ? "-" : "") . $permission->getName(); 98 | $k = $this->resolveRoleIndex($role->getId()); 99 | if(!self::permissionInArray($permission, $this->roles[$k]["Permissions"])) { 100 | $this->roles[$k]["Permissions"][] = $permission; 101 | } 102 | $this->reIndex($this->roles[$k]["Permissions"]); 103 | $this->flush(); 104 | } 105 | 106 | public function removeRolePermission(Role $role, $permission): void { 107 | if($permission instanceof Permission) { 108 | $permission = $permission->getName(); 109 | } 110 | $k = $this->resolveRoleIndex($role->getId()); 111 | self::removePermissionFromArray($permission, $this->roles[$k]["Permissions"]); 112 | $this->reIndex($this->roles[$k]["Permissions"]); 113 | $this->flush(); 114 | } 115 | 116 | public function createRoleOnStorage(string $name, int $id, int $position): void { 117 | array_splice($this->roles, $position, 0, [[ 118 | "ID" => $id, 119 | "Name" => $name, 120 | "isDefault" => false, 121 | "Permissions" => [] 122 | ]]); 123 | $this->flush(); 124 | } 125 | 126 | public function deleteRoleFromStorage(Role $role): void { 127 | unset($this->roles[$this->resolveRoleIndex($role->getId())]); 128 | $this->flush(); 129 | } 130 | 131 | /** 132 | * @param int $roleID 133 | * 134 | * @return int 135 | * @throws UnresolvedRoleException 136 | */ 137 | private function resolveRoleIndex(int $roleID): int { 138 | foreach($this->roles as $i => $role) { 139 | if($role["ID"] == $roleID) { 140 | return $i; 141 | } 142 | } 143 | throw new UnresolvedRoleException("Unable to resolve unknown role with ID {$roleID}"); 144 | } 145 | 146 | public function shutdown(): void { 147 | // should be saved already 148 | } 149 | 150 | /** 151 | * @param array $roles 152 | * @internal Only used for writing data directly 153 | */ 154 | public function setRoles(array $roles): void { 155 | $this->roles = $roles; 156 | } 157 | 158 | public function flush(): void { 159 | ksort($this->roles); 160 | file_put_contents($this->rolesFile, $this->encode($this->roles)); 161 | } 162 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/role/JSONRoleDS.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\role; 31 | 32 | 33 | use CortexPE\Hierarchy\data\traits\JSONProcessing; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | 36 | class JSONRoleDS extends IndexedRoleDS { 37 | use JSONProcessing; 38 | 39 | /** @var string */ 40 | protected const FILE_EXTENSION = "json"; 41 | 42 | public function __construct(Hierarchy $plugin, array $config) { 43 | $this->readConfig($config); 44 | parent::__construct($plugin); 45 | } 46 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/role/RoleDataSource.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\role; 31 | 32 | 33 | use CortexPE\Hierarchy\data\DataSource; 34 | use CortexPE\Hierarchy\role\Role; 35 | use pocketmine\permission\Permission; 36 | 37 | abstract class RoleDataSource extends DataSource { 38 | /** 39 | * @param Role $role 40 | * @param Permission $permission 41 | * @param bool $inverted 42 | * 43 | * @internal Add role permission 44 | * 45 | */ 46 | abstract public function addRolePermission(Role $role, Permission $permission, bool $inverted = false): void; 47 | 48 | /** 49 | * @param Role $role 50 | * @param Permission|string $permission 51 | * 52 | * @internal Remove role permission 53 | * 54 | */ 55 | abstract public function removeRolePermission(Role $role, $permission): void; 56 | 57 | /** 58 | * @param string $name 59 | * @param int $id 60 | * @param int $position 61 | * 62 | * @internal Create role on storage 63 | */ 64 | abstract public function createRoleOnStorage(string $name, int $id, int $position): void; 65 | 66 | /** 67 | * @param Role $role 68 | * 69 | * @internal Delete role from storage 70 | */ 71 | abstract public function deleteRoleFromStorage(Role $role): void; 72 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/role/YAMLRoleDS.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\role; 31 | 32 | 33 | use CortexPE\Hierarchy\data\traits\YAMLProcessing; 34 | 35 | class YAMLRoleDS extends IndexedRoleDS { 36 | use YAMLProcessing; 37 | 38 | /** @var string */ 39 | protected const FILE_EXTENSION = "yml"; 40 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/traits/IndexedDataUtilities.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\traits; 31 | 32 | 33 | use function array_search; 34 | use function array_unique; 35 | use function array_values; 36 | use function in_array; 37 | 38 | trait IndexedDataUtilities { 39 | private static function permissionInArray(string $permission, array $array): bool { 40 | return in_array($permission, $array) || in_array("-" . $permission, $array); 41 | } 42 | 43 | private static function removePermissionFromArray(string $permission, array &$array): void { 44 | if(self::permissionInArray($permission, $array)) { 45 | unset( 46 | $array[array_search($permission, $array)], 47 | $array[array_search("-" . $permission, $array)] 48 | ); 49 | } 50 | } 51 | 52 | private function reIndex(array &$array): void { 53 | $array = array_values(array_unique($array)); 54 | } 55 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/traits/JSONProcessing.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\traits; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use function json_decode; 35 | use function json_encode; 36 | use const JSON_PRETTY_PRINT; 37 | 38 | trait JSONProcessing { 39 | /** @var bool */ 40 | protected $prettyPrint = false; 41 | 42 | protected function readConfig(array $config) : void{ 43 | $this->prettyPrint = (bool)$config["prettyPrint"]; 44 | } 45 | 46 | public function encode(array $data): string { 47 | return json_encode($data, $this->prettyPrint ? JSON_PRETTY_PRINT : 0); 48 | } 49 | 50 | public function decode(string $string): array { 51 | return json_decode($string, true); 52 | } 53 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/data/traits/YAMLProcessing.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\data\traits; 31 | 32 | 33 | trait YAMLProcessing { 34 | public function encode(array $data): string { 35 | return yaml_emit($data, YAML_UTF8_ENCODING); 36 | } 37 | 38 | public function decode(string $string): array { 39 | return yaml_parse($string); 40 | } 41 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/event/MemberRoleAddEvent.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\event; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | 36 | class MemberRoleAddEvent extends MemberRoleUpdateEvent { 37 | /** @var Role */ 38 | protected $added; 39 | 40 | public function __construct(BaseMember $member, Role $added) { 41 | parent::__construct($member); 42 | $this->added = $added; 43 | } 44 | 45 | /** 46 | * @return Role 47 | */ 48 | public function getRoleAdded(): Role { 49 | return $this->added; 50 | } 51 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/event/MemberRoleRemoveEvent.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\event; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use CortexPE\Hierarchy\role\Role; 35 | 36 | class MemberRoleRemoveEvent extends MemberRoleUpdateEvent { 37 | /** @var Role */ 38 | protected $removed; 39 | 40 | public function __construct(BaseMember $member, Role $removed) { 41 | parent::__construct($member); 42 | $this->removed = $removed; 43 | } 44 | 45 | /** 46 | * @return Role 47 | */ 48 | public function getRoleRemoved(): Role { 49 | return $this->removed; 50 | } 51 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/event/MemberRoleUpdateEvent.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\event; 31 | 32 | 33 | use CortexPE\Hierarchy\member\BaseMember; 34 | use pocketmine\event\Event; 35 | 36 | class MemberRoleUpdateEvent extends Event { 37 | /** @var BaseMember */ 38 | protected $member; 39 | 40 | public function __construct(BaseMember $member) { 41 | $this->member = $member; 42 | } 43 | 44 | /** 45 | * @return BaseMember 46 | */ 47 | public function getMember(): BaseMember { 48 | return $this->member; 49 | } 50 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/exception/HierarchyException.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\exception; 31 | 32 | 33 | class HierarchyException extends \Exception { 34 | 35 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/exception/RoleCollissionError.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\exception; 31 | 32 | 33 | class RoleCollissionError extends HierarchyException { 34 | 35 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/exception/StartupFailureException.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\exception; 31 | 32 | 33 | class UnexpectedDataTypeError extends HierarchyException { 34 | 35 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/exception/UnknownPermissionNode.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\exception; 31 | 32 | 33 | class UnknownPermissionNode extends HierarchyException { 34 | 35 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/exception/UnresolvedRoleException.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\lang; 31 | 32 | 33 | use pocketmine\utils\Config; 34 | use pocketmine\utils\TextFormat; 35 | use RuntimeException; 36 | 37 | class MessageStore { 38 | /** @var Config */ 39 | protected static $config; 40 | /** @var array */ 41 | protected static $defaults = [ 42 | "cmd.clear_all.success" => "&cRemoved all roles and permissions from {target}", 43 | 44 | "cmd.createrole.success" => "&aSuccessfully created role with name '{role}' with the ID: {role_id}", 45 | 46 | "cmd.deleterole.fail_role_default" => "&eYou cannot delete the default role.", 47 | "cmd.deleterole.success" => "&eSuccessfully deleted {role} ({role_id}) role", 48 | 49 | "cmd.denyperm.form.choose_type" => "Please choose the target type you'd like to deny permissions from, from the options below", 50 | "cmd.denyperm.form.type_role" => "Role", 51 | "cmd.denyperm.form.type_member" => "Member", 52 | "cmd.denyperm.role.success" => "&eSuccessfully denied the permission '{permission}' from Role {role}({role_id})", 53 | "cmd.denyperm.member.success" => "&eSuccessfully denied the permission '{permission}' from {member}", 54 | 55 | "cmd.flush.success" => "&aSuccessfully flushed role data to disk", 56 | 57 | "cmd.giverole.success" => "&aGiven '{role}' role to {member}", 58 | "cmd.giverole.has_role" => "&c{member} already has the role '{role}'", 59 | "cmd.giverole.default" => "&c{member} already has the default role", 60 | 61 | "cmd.grantperm.form.choose_type" => "Please choose the target type you'd like to grant permissions to from below", 62 | "cmd.grantperm.form.type_role" => "Role", 63 | "cmd.grantperm.form.type_member" => "Member", 64 | "cmd.grantperm.role.success" => "&aSuccessfully added the permission '{permission}' to Role {role}({role_id})", 65 | "cmd.grantperm.member.success" => "&aSuccessfully added the permission '{permission}' to {member}", 66 | 67 | "cmd.info.menu_form.description" => "Choose target:", 68 | "cmd.info.member_form.instruction" => "Enter member to get info from:", 69 | "cmd.info.member_form.opt_text" => "Member Name", 70 | "cmd.info.role_form.instruction" => "Select role to get info from:", 71 | "cmd.info.role_form.opt_text" => "Select Role", 72 | 73 | "cmd.info.member.header" => "&9 ----- Member Info for '&b{member}&9' ----- ", 74 | "cmd.info.member.roles_header" => "&e&lRoles:", 75 | "cmd.info.member.role_entry" => "&e - &6{role} ({role_id})", 76 | "cmd.info.member.m_perms_header" => "&e&lMember Permission Overrides:", 77 | "cmd.info.member.m_perm_entry" => "&e - {color}{permission}", 78 | "cmd.info.member.no_extra_perms" => "&e - &cNo extra member permissions", 79 | 80 | "cmd.info.role.header" => "&9 ----- Role Info for &b{role} ({role_id})&9 ----- ", 81 | "cmd.info.role.position" => "&e&lPosition: &r&6{position}", 82 | "cmd.info.role.default" => "&e&lDefault Role: &r{isDefault}", 83 | "cmd.info.role.perms_header" => "&e&lPermissions:", 84 | "cmd.info.role.perm_entry" => "&e - {color}{permission}", 85 | "cmd.info.role.members_header" => "&e&lOnline Members ({count}):", 86 | "cmd.info.role.member_entry" => "&e - {member}", 87 | "cmd.info.role.no_online_members" => "&e - &cNo online members", 88 | "cmd.info.role.offline_members_header" => "&e&lOffline Members ({count}):", 89 | "cmd.info.role.offline_member_entry" => "&e - {member}", 90 | "cmd.info.role.no_offline_members" => "&e - &cNo offline members", 91 | 92 | "cmd.info.role_list.header" => "&9&lAvailable Roles ({count}):", 93 | "cmd.info.role_list.entry" => "&e - {role} ({role_id})", 94 | 95 | "cmd.info.perm_list.header" => "&9&lAvailable Permissions ({count}):", 96 | "cmd.info.perm_list.entry" => "&e - {permission}", 97 | 98 | "cmd.revokeperm.form.choose_type" => "Please choose the target type you'd like to remove permissions from, from the options below", 99 | "cmd.revokeperm.form.type_role" => "Role", 100 | "cmd.revokeperm.form.type_member" => "Member", 101 | "cmd.revokeperm.role.success" => "&eSuccessfully removed the permission '{permission}' from Role {role}({role_id})", 102 | "cmd.revokeperm.member.success" => "&eSuccessfully removed the permission '{permission}' from {member}", 103 | 104 | "cmd.takerole.success" => "&eRemoved '{role}' role from {member}", 105 | "cmd.takerole.no_role" => "&c{member} does not have the '{role}' role", 106 | "cmd.takerole.default" => "&cCannot remove default role from {member}", 107 | 108 | "cmd.transfer_privileges.same_member" => "&cCannot transfer privileges to the same player.", 109 | "cmd.transfer_privileges.success" => "&aTransferred privileges from {source} to {target}", 110 | 111 | "err.target_higher_hrk" => "&cYou cannot use this command on '{target}' due to higher role hierarchy", 112 | "err.unknown_permission" => "&cUnknown permission node.", 113 | "err.unknown_role" => "&cRole not found. For a complete list of roles, please use '/hrk info role_list'", 114 | "err.no_permissions" => "&cThis role is not assigned any permissions", 115 | "err.no_roles" => "&cThere are no roles setup", 116 | "err.no_roles_for_action" => "&cThere are no roles available for this action", 117 | "err.player_only" => "&cThis command is player only", 118 | ]; 119 | 120 | public function __construct(string $filePath, int $type = Config::YAML, ?array $defaults = null) { 121 | if($defaults !== null) { 122 | static::$defaults = $defaults; 123 | } 124 | if(empty(static::$defaults)) { 125 | throw new RuntimeException("No defaults given to message store instance for " . get_class($this)); 126 | } 127 | static::$config = new Config($filePath, $type, static::$defaults); 128 | } 129 | 130 | public static function getMessage( 131 | string $dataKey, 132 | array $args = [], 133 | string $prefix = "{", 134 | string $suffix = "}" 135 | ): string { 136 | return TextFormat::colorize( 137 | self::substituteString(static::getMessageRaw($dataKey), $args, $prefix, $suffix), 138 | "&" 139 | ); 140 | } 141 | 142 | protected static function substituteString(string $str, array $args, string $prefix, string $suffix): string { 143 | foreach($args as $item => $value) { 144 | $str = str_ireplace($prefix . $item . $suffix, $value, $str); 145 | } 146 | 147 | return $str; 148 | } 149 | 150 | public static function getMessageRaw(string $dataKey): string { 151 | return (string)static::$config->get($dataKey, static::$defaults[$dataKey]); 152 | } 153 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/member/BaseMember.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\member; 31 | 32 | 33 | use CortexPE\Hierarchy\data\member\MemberDataSource; 34 | use CortexPE\Hierarchy\event\MemberRoleAddEvent; 35 | use CortexPE\Hierarchy\event\MemberRoleRemoveEvent; 36 | use CortexPE\Hierarchy\Hierarchy; 37 | use CortexPE\Hierarchy\role\Role; 38 | use pocketmine\permission\Permission; 39 | use pocketmine\permission\PermissionManager; 40 | use pocketmine\Player; 41 | use pocketmine\Server; 42 | use function in_array; 43 | use function substr; 44 | 45 | abstract class BaseMember { 46 | /** @var Hierarchy */ 47 | protected $plugin; 48 | /** @var MemberDataSource */ 49 | protected $dataSource; 50 | /** @var PermissionManager */ 51 | protected $permMgr; 52 | /** @var bool[] */ 53 | protected $permissions = [], $memberPermissions = []; // (string) PermissionNode => (bool) ALLOW/DISALLOW 54 | /** @var Role[] */ 55 | protected $roles = []; 56 | /** @var Server */ 57 | protected $server; 58 | /** @var array */ 59 | protected $roleAdditionalData = []; 60 | /** @var array */ 61 | protected $permissionAdditionalData = []; 62 | 63 | public function __construct(Hierarchy $plugin) { 64 | $this->plugin = $plugin; 65 | $this->dataSource = $plugin->getMemberDataSource(); 66 | $this->permMgr = PermissionManager::getInstance(); 67 | $this->server = $plugin->getServer(); 68 | } 69 | 70 | /** 71 | * Returns a list of all the Roles a member has 72 | * 73 | * @return Role[] 74 | */ 75 | public function getRoles(): array { 76 | return $this->roles; 77 | } 78 | 79 | /** 80 | * @internal 81 | * 82 | * @param array $memberData 83 | */ 84 | public function loadData(array $memberData): void { 85 | foreach($memberData["roles"] ?? [] as $roleId => $additionalData) { 86 | $role = $this->plugin->getRoleManager()->getRole($roleId); 87 | if($role instanceof Role) { 88 | if($additionalData !== null) { 89 | $this->roleAdditionalData[$roleId] = $additionalData; 90 | } 91 | $this->roles[$roleId] = $role; 92 | } else { 93 | // un-existent role 94 | $this->plugin->getLogger()->debug("Ignoring non-existent role ID $roleId from " . $this->getName()); 95 | } 96 | } 97 | foreach($memberData["permissions"] ?? [] as $perm => $additionalData) { 98 | $inverted = false; 99 | if($perm{0} === "-") { 100 | $inverted = true; 101 | $perm = substr($perm, 1); 102 | } 103 | $permission = $this->permMgr->getPermission($perm); 104 | if($permission instanceof Permission) { 105 | if($additionalData !== null){ 106 | $this->permissionAdditionalData[$permission->getName()] = $additionalData; 107 | } 108 | $this->memberPermissions[$permission->getName()] = !$inverted; 109 | } // ignore missing permissions 110 | } 111 | $this->recalculatePermissions(); 112 | } 113 | 114 | /** 115 | * Allow a member to use a permission 116 | * 117 | * @param Permission $permission 118 | * @param bool $recalculate 119 | * @param bool $save 120 | */ 121 | public function addMemberPermission(Permission $permission, bool $recalculate = true, bool $save = true): void { 122 | $permission = $permission->getName(); 123 | $this->memberPermissions[$permission] = true; 124 | if($recalculate) { 125 | $this->recalculatePermissions(); 126 | } 127 | if($save) { 128 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_PERMS_ADD, $permission); 129 | } 130 | } 131 | 132 | /** 133 | * Deny a member from using a permission 134 | * 135 | * @param Permission $permission 136 | * @param bool $recalculate 137 | * @param bool $save 138 | */ 139 | public function denyMemberPermission(Permission $permission, bool $recalculate = true, bool $save = true): void { 140 | $permission = $permission->getName(); 141 | $this->memberPermissions[$permission] = false; 142 | if($recalculate) { 143 | $this->recalculatePermissions(); 144 | } 145 | if($save) { 146 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_PERMS_ADD, "-" . $permission); 147 | } 148 | } 149 | 150 | /** 151 | * This function allows the removal of permissions based on their name because it may be a missing permission 152 | * intentionally being removed... A consideration for multi-server scenarios. 153 | * 154 | * @param Permission|string $permission 155 | * @param bool $recalculate 156 | * @param bool $save 157 | */ 158 | public function removeMemberPermission($permission, bool $recalculate = true, bool $save = true): void { 159 | if($permission instanceof Permission) { 160 | $permission = $permission->getName(); 161 | } 162 | unset($this->memberPermissions[$permission]); 163 | unset($this->permissionAdditionalData[$permission]); 164 | if($recalculate) { 165 | $this->recalculatePermissions(); 166 | } 167 | if($save){ 168 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_PERMS_REMOVE, $permission); 169 | } 170 | } 171 | 172 | /** 173 | * Gives the member a role 174 | * 175 | * @param Role $role 176 | * @param bool $recalculate 177 | * @param bool $save 178 | */ 179 | public function addRole(Role $role, bool $recalculate = true, bool $save = true): void { 180 | if(!$this->hasRole($role)) { 181 | $this->roles[$role->getId()] = $role; 182 | $this->onRoleAdd($role); 183 | (new MemberRoleAddEvent($this, $role))->call(); 184 | if($save) { 185 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_ROLE_ADD, $role->getId()); 186 | } 187 | 188 | if($recalculate) { 189 | $this->recalculatePermissions(); 190 | } 191 | } 192 | } 193 | 194 | /** 195 | * Called after a role is added, before being saved into the database. Used for giving a reference to the member 196 | * to a role on Online Members. 197 | * 198 | * @param Role $role 199 | */ 200 | abstract protected function onRoleAdd(Role $role): void; 201 | 202 | /** 203 | * Allows to check if the member has a specific role. 204 | * 205 | * @param Role $role 206 | * @return bool 207 | */ 208 | public function hasRole(Role $role): bool { 209 | return in_array($role, $this->roles, true); 210 | } 211 | 212 | /** 213 | * Called to recalculate member permissions. 214 | */ 215 | public function recalculatePermissions(): void { 216 | $this->permissions = []; 217 | $perms = []; // default 218 | foreach($this->roles as $role) { 219 | $perms[$role->getPosition()] = $role->getCombinedPermissions(); 220 | } 221 | $perms[PHP_INT_MAX] = $this->memberPermissions; // this overrides other permissions 222 | krsort($perms); 223 | $this->permissions = array_replace_recursive(...$perms); 224 | } 225 | 226 | /** 227 | * Removes all roles that are loaded. (This excludes roles that are missing) 228 | * 229 | * @param bool $recalculate 230 | * @param bool $save 231 | */ 232 | public function clearRoles(bool $recalculate = true, bool $save = true): void { 233 | foreach($this->roles as $role) { 234 | $this->removeRole($role, false, $save); 235 | } 236 | $this->roles = []; 237 | if($recalculate) { 238 | $this->recalculatePermissions(); 239 | } 240 | } 241 | 242 | /** 243 | * Removes a specific role 244 | * 245 | * @param Role $role 246 | * @param bool $recalculate 247 | * @param bool $save 248 | */ 249 | public function removeRole(Role $role, bool $recalculate = true, bool $save = true): void { 250 | if($this->hasRole($role)) { 251 | unset($this->roles[$role->getId()]); 252 | unset($this->roleAdditionalData[$role->getId()]); 253 | $this->onRoleRemove($role); 254 | (new MemberRoleRemoveEvent($this, $role))->call(); 255 | if($save) { 256 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_ROLE_REMOVE, $role->getId()); 257 | } 258 | if($recalculate) { 259 | $this->recalculatePermissions(); 260 | } 261 | } 262 | } 263 | 264 | abstract protected function onRoleRemove(Role $role): void; 265 | 266 | public function getTopRole(): Role { 267 | $maxPos = $basePos = ($defaultRole = $this->plugin->getRoleManager()->getDefaultRole())->getPosition(); 268 | $ri = 0; 269 | foreach($this->roles as $i => $role) { 270 | if($role->getPosition() > $maxPos) { 271 | $maxPos = $role->getPosition(); 272 | $ri = $i; 273 | } 274 | } 275 | if($maxPos === $basePos) { 276 | return $defaultRole; 277 | } 278 | return $this->roles[$ri]; 279 | } 280 | 281 | /** 282 | * @return bool[] 283 | */ 284 | public function getPermissions(): array { 285 | return $this->permissions; 286 | } 287 | 288 | /** 289 | * @param Permission|string $permissionNode 290 | * @param BaseMember $target 291 | * 292 | * @return bool 293 | */ 294 | public function hasHigherPermissionHierarchy($permissionNode, BaseMember $target): bool { 295 | if($permissionNode instanceof Permission) { 296 | $permissionNode = $permissionNode->getName(); 297 | } 298 | $myTopRole = $this->getTopRoleWithPermission($permissionNode); 299 | if($myTopRole instanceof Role) { 300 | $targetTopRole = $target->getTopRoleWithPermission($permissionNode); 301 | if($targetTopRole instanceof Role) { 302 | return $myTopRole->getPosition() > $targetTopRole->getPosition(); 303 | } 304 | 305 | return true; 306 | } 307 | 308 | return false; 309 | } 310 | 311 | /** 312 | * @param Permission|string $permissionNode 313 | * 314 | * @return Role|null 315 | */ 316 | public function getTopRoleWithPermission($permissionNode): ?Role { 317 | if($permissionNode instanceof Permission) { 318 | $permissionNode = $permissionNode->getName(); 319 | } 320 | $topRolePosition = PHP_INT_MIN; 321 | $topRoleWithPerm = null; 322 | foreach($this->roles as $role) { 323 | if( 324 | isset(($role->getCombinedPermissions())[$permissionNode]) && 325 | $role->getPosition() > $topRolePosition 326 | ) { 327 | $topRolePosition = $role->getPosition(); 328 | $topRoleWithPerm = $role; 329 | } 330 | } 331 | 332 | return $topRoleWithPerm; 333 | } 334 | 335 | /** 336 | * @return bool[] 337 | */ 338 | public function getMemberPermissions(): array { 339 | return $this->memberPermissions; 340 | } 341 | 342 | abstract public function getPlayer(): ?Player; 343 | 344 | abstract public function getName(): string; 345 | 346 | /** 347 | * @internal 348 | */ 349 | abstract public function onDestroy(): void; 350 | 351 | /** 352 | * @return array 353 | */ 354 | public function getRoleAdditionalData(): array { 355 | return $this->roleAdditionalData; 356 | } 357 | 358 | /** 359 | * @return array 360 | */ 361 | public function getPermissionAdditionalData(): array { 362 | return $this->permissionAdditionalData; 363 | } 364 | 365 | /** 366 | * @param Role $role 367 | * @param array $data 368 | */ 369 | public function setRoleAdditionalData(Role $role, array $data):void { 370 | $this->roleAdditionalData[$role->getId()] = $data; 371 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_UPDATE_ROLE_ETC, [$role->getId(), $data]); 372 | } 373 | 374 | /** 375 | * @param Permission $permission 376 | * @param array $data 377 | */ 378 | public function setPermissionAdditionalData(Permission $permission, array $data):void { 379 | $this->permissionAdditionalData[$permission->getName()] = $data; 380 | $this->dataSource->updateMemberData($this, MemberDataSource::ACTION_MEMBER_UPDATE_PERMISSION_ETC, [$permission->getName(), $data]); 381 | } 382 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/member/Member.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\member; 31 | 32 | 33 | use CortexPE\Hierarchy\event\MemberRoleUpdateEvent; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | use CortexPE\Hierarchy\role\Role; 36 | use pocketmine\permission\PermissionAttachment; 37 | use pocketmine\Player; 38 | 39 | class Member extends BaseMember { 40 | /** @var Player */ 41 | protected $player; 42 | /** @var PermissionAttachment */ 43 | protected $attachment; 44 | 45 | public function __construct(Hierarchy $plugin, Player $player) { 46 | parent::__construct($plugin); 47 | $this->player = $player; 48 | $this->attachment = $player->addAttachment($plugin); 49 | } 50 | 51 | /** 52 | * @return PermissionAttachment|null 53 | */ 54 | public function getAttachment(): ?PermissionAttachment { 55 | return $this->attachment; 56 | } 57 | 58 | public function recalculatePermissions(): void { 59 | parent::recalculatePermissions(); 60 | $this->attachment->clearPermissions(); 61 | $this->attachment->setPermissions($this->permissions); 62 | } 63 | 64 | /** 65 | * @return Player 66 | */ 67 | public function getPlayer(): Player { 68 | return $this->player; 69 | } 70 | 71 | public function getName(): string { 72 | return $this->player->getName(); 73 | } 74 | 75 | public function loadData(array $memberData): void { 76 | parent::loadData($memberData); 77 | foreach($this->roles as $role) { 78 | $role->bind($this); 79 | } 80 | 81 | // broadcast that our data has loaded, and our roles has updated from being empty 82 | (new MemberRoleUpdateEvent($this))->call(); 83 | } 84 | 85 | protected function onRoleAdd(Role $role): void { 86 | $role->bind($this); 87 | } 88 | 89 | protected function onRoleRemove(Role $role): void { 90 | $role->unbind($this); 91 | } 92 | 93 | public function onDestroy(): void { 94 | foreach($this->roles as $k => $role) { 95 | $role->unbind($this); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/member/MemberFactory.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\member; 31 | 32 | 33 | use CortexPE\Hierarchy\data\member\SQLMemberDS; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | use pocketmine\OfflinePlayer; 36 | use pocketmine\Player; 37 | use function is_string; 38 | 39 | class MemberFactory { 40 | /** @var Hierarchy */ 41 | protected $plugin; 42 | /** @var Member[] */ 43 | protected $onlineMembers = []; 44 | 45 | public function __construct(Hierarchy $plugin) { 46 | $this->plugin = $plugin; 47 | } 48 | 49 | public function createSession(Player $player): void { 50 | $this->getMember($player); // just call this function, does the same thing 51 | } 52 | 53 | /** 54 | * @param Player|OfflinePlayer|string $player 55 | * 56 | * @return BaseMember 57 | */ 58 | public function getMember($player): BaseMember { 59 | if(is_string($player)) { 60 | $player = $this->plugin->getServer()->getOfflinePlayer((string)$player); 61 | } 62 | $newMember = false; 63 | if($player instanceof Player) { 64 | if(!isset($this->onlineMembers[($n = $player->getName())])) { 65 | $this->onlineMembers[$n] = new Member($this->plugin, $player); 66 | $newMember = true; 67 | $this->plugin->getLogger()->debug("Created {$player->getName()}'s Session"); 68 | } 69 | $m = $this->onlineMembers[$n]; 70 | } else { 71 | $m = new OfflineMember($this->plugin, $player->getName()); 72 | $newMember = true; 73 | } 74 | if($newMember) { 75 | ($ds = $this->plugin->getMemberDataSource())->loadMemberData($m); 76 | if($m instanceof OfflineMember && $ds instanceof SQLMemberDS) { 77 | /** 78 | * TODO: 79 | * Make this better... 80 | * the sole reason this hack exists is because of the typical usage of OfflineMember, 81 | * data has to be manipulated right away therefore it has to be available right away. 82 | */ 83 | $ds->getDB()->waitAll(); 84 | } 85 | } 86 | 87 | return $m; 88 | } 89 | 90 | public function shutdown(): void { 91 | foreach($this->onlineMembers as $member) { 92 | $this->destroySession($member->getPlayer()); 93 | } 94 | } 95 | 96 | public function destroySession(Player $player): void { 97 | $this->plugin->getLogger()->debug("Destroying {$player->getName()}'s Session"); 98 | $k = $player->getName(); 99 | if(isset($this->onlineMembers[$k])) { 100 | $this->onlineMembers[$k]->onDestroy();; 101 | } 102 | unset($this->onlineMembers[$k]); 103 | } 104 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/member/OfflineMember.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\member; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use CortexPE\Hierarchy\role\Role; 35 | use pocketmine\Player; 36 | 37 | class OfflineMember extends BaseMember { 38 | /** @var string */ 39 | protected $username; 40 | 41 | public function __construct(Hierarchy $plugin, string $username) { 42 | parent::__construct($plugin); 43 | $this->username = $username; 44 | } 45 | 46 | public function getPlayer(): ?Player { 47 | return $this->server->getPlayerExact($this->username); 48 | } 49 | 50 | public function getName(): string { 51 | return $this->username; 52 | } 53 | 54 | protected function onRoleAdd(Role $role): void { 55 | // noop 56 | } 57 | 58 | protected function onRoleRemove(Role $role): void { 59 | // noop 60 | } 61 | 62 | public function onDestroy(): void { 63 | // noop 64 | } 65 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/role/Role.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\role; 31 | 32 | 33 | use CortexPE\Hierarchy\exception\HierarchyException; 34 | use CortexPE\Hierarchy\Hierarchy; 35 | use CortexPE\Hierarchy\member\BaseMember; 36 | use CortexPE\Hierarchy\member\Member; 37 | use CortexPE\Hierarchy\member\OfflineMember; 38 | use pocketmine\permission\Permission; 39 | use pocketmine\permission\PermissionManager; 40 | use pocketmine\utils\Utils; 41 | use SOFe\AwaitGenerator\Await; 42 | use function substr; 43 | 44 | class Role { 45 | /** @var Hierarchy */ 46 | protected $plugin; 47 | 48 | /** @var int */ 49 | protected $id; 50 | /** @var string */ 51 | protected $name; 52 | 53 | /** @var int */ 54 | protected $position; 55 | /** @var bool[] */ 56 | protected $permissions = []; 57 | /** @var bool[] */ 58 | protected $extraPermissions = []; 59 | /** @var bool */ 60 | protected $isDefault = false; 61 | 62 | /** @var Member[] */ 63 | protected $onlineMembers = []; 64 | /** @var Role[] */ 65 | protected $parents = []; 66 | /** @var Role[] */ 67 | protected $children = []; 68 | /** @var bool[] */ 69 | protected $combinedPermissions; 70 | 71 | /** @var bool */ 72 | protected $hasAllPermissions = false; 73 | 74 | public function __construct(Hierarchy $plugin, int $id, string $name, array $roleData) { 75 | $this->plugin = $plugin; 76 | $this->id = $id; 77 | $this->name = $name; 78 | $this->position = $roleData["position"]; 79 | $this->isDefault = (bool)($roleData["isDefault"] ?? false); 80 | } 81 | 82 | /** 83 | * @return int 84 | */ 85 | public function getId(): int { 86 | return $this->id; 87 | } 88 | 89 | /** 90 | * @return string 91 | */ 92 | public function getName(): string { 93 | return $this->name; 94 | } 95 | 96 | /** 97 | * @return int 98 | */ 99 | public function getPosition(): int { 100 | return $this->position; 101 | } 102 | 103 | /** 104 | * @return bool[] 105 | */ 106 | public function getPermissions(): array { 107 | return $this->permissions; 108 | } 109 | 110 | public function bind(BaseMember $member): void { 111 | $this->onlineMembers[strtolower($member->getName())] = $member; 112 | } 113 | 114 | public function unbind(BaseMember $member): void { 115 | unset($this->onlineMembers[strtolower($member->getName())]); 116 | } 117 | 118 | /** 119 | * @return Member[] 120 | */ 121 | public function getOnlineMembers(): array { 122 | return $this->onlineMembers; 123 | } 124 | 125 | /** 126 | * Triggers the callback with a list of OfflineMember objects 127 | * 128 | * @param callable $callback 129 | * @throws HierarchyException 130 | */ 131 | public function getOfflineMembers(callable $callback): void { 132 | Utils::validateCallableSignature(function(array $members):void{}, $callback); 133 | if($this->isDefault){ // basically everyone who joined the server and will ever join the server 134 | throw new HierarchyException("Cannot get offline members of default role"); 135 | } 136 | Await::f2c(function() use ($callback):\Generator{ 137 | $members = []; 138 | foreach(yield $this->plugin->getMemberDataSource()->getMemberNamesOf($this) as $row){ 139 | $n = strtolower($row["Player"]); 140 | if(isset($this->onlineMembers[$n])){ 141 | continue; 142 | } 143 | if(isset($members[$n])){ 144 | continue; 145 | } 146 | $members[$n] = new OfflineMember($this->plugin, $row["Player"]); 147 | } 148 | $callback($members); 149 | }, null, function(\Throwable $e):void{ 150 | $this->plugin->getLogger()->logException($e); 151 | }); 152 | } 153 | 154 | /** 155 | * @return bool 156 | */ 157 | public function isDefault(): bool { 158 | return $this->isDefault; 159 | } 160 | 161 | /** 162 | * @param Permission $permission 163 | * @param bool $update 164 | */ 165 | public function addPermission(Permission $permission, bool $update = true): void { 166 | $this->permissions[$permission->getName()] = true; 167 | $this->recalculateCombinedPermissions(); 168 | $this->plugin->getRoleDataSource()->addRolePermission($this, $permission, false); 169 | if($update) { 170 | $this->updateChildrenPermissions(); 171 | $this->updateMemberPermissions(); 172 | } 173 | } 174 | 175 | public function denyPermission(Permission $permission, bool $update = true): void { 176 | $this->permissions[$permission->getName()] = false; 177 | $this->recalculateCombinedPermissions(); 178 | $this->plugin->getRoleDataSource()->addRolePermission($this, $permission, true); 179 | if($update) { 180 | $this->updateChildrenPermissions(); 181 | $this->updateMemberPermissions(); 182 | } 183 | } 184 | 185 | /** 186 | * @param Permission|string $permission 187 | * @param bool $update 188 | */ 189 | public function removePermission($permission, bool $update = true): void { 190 | if($permission instanceof Permission) { 191 | $permission = $permission->getName(); 192 | } 193 | $this->removePermissionInternal($permission); 194 | $this->plugin->getRoleDataSource()->removeRolePermission($this, $permission); 195 | if($update) { 196 | $this->updateMemberPermissions(); 197 | } 198 | } 199 | 200 | /** 201 | * @internal 202 | * 203 | * @param string $permission 204 | */ 205 | public function removePermissionInternal(string $permission): void { 206 | unset($this->permissions[$permission]); 207 | $this->recalculateCombinedPermissions(); 208 | $this->updateChildrenPermissions(); 209 | } 210 | 211 | public function updateMemberPermissions(): void { 212 | foreach($this->onlineMembers as $member) { 213 | $member->recalculatePermissions(); 214 | } 215 | } 216 | 217 | /** 218 | * @internal Adds 1 to the role position to make way for a newly created role 219 | */ 220 | public function bumpPosition(): void { 221 | $this->position++; 222 | $this->updateMemberPermissions(); 223 | } 224 | 225 | /** 226 | * @internal Used internally for loading permissions from an inherited role 227 | * 228 | * @param Role $role 229 | */ 230 | public function inheritRole(Role $role):void { 231 | $this->parents[$role->getId()] = $role; 232 | $role->addChild($this); 233 | } 234 | 235 | /** 236 | * @internal Used internally for loading permissions from an inherited role 237 | * 238 | * @param Role $role 239 | */ 240 | public function unInheritRole(Role $role):void { 241 | unset($this->parents[$role->getId()]); 242 | $role->removeChild($this); 243 | $this->refreshInheritedPermissions(); 244 | $this->recalculateCombinedPermissions(); 245 | } 246 | 247 | /** 248 | * @internal Used internally for loading permissions from storage 249 | * 250 | * @param array $permissions 251 | */ 252 | public function loadPermissions(array $permissions):void { 253 | $pMgr = PermissionManager::getInstance(); 254 | foreach($permissions ?? [] as $permission) { 255 | if($permission == "*") { 256 | $this->hasAllPermissions = true; 257 | foreach($pMgr->getPermissions() as $perm){ 258 | $this->permissions[$perm->getName()] = true; 259 | } 260 | continue; 261 | } 262 | $value = true; 263 | if(substr($permission, 0, 1) === "-"){ 264 | $value = false; 265 | $permission = substr($permission, 1); 266 | } 267 | $this->permissions[$permission] = $value; 268 | } 269 | $this->refreshInheritedPermissions(); 270 | $this->recalculateCombinedPermissions(); 271 | } 272 | 273 | public function updateChildrenPermissions(): void { 274 | foreach($this->children as $role) { 275 | $role->refreshInheritedPermissions(); 276 | } 277 | } 278 | 279 | public function refreshInheritedPermissions():void { 280 | $this->extraPermissions = []; 281 | foreach($this->parents as $role){ 282 | foreach($role->getPermissions() as $permission => $toggle){ 283 | $this->extraPermissions[$permission] = $toggle; 284 | } 285 | } 286 | } 287 | 288 | /** 289 | * @param Role $child 290 | * @internal Used for keeping references to roles inheriting its permissions 291 | */ 292 | public function addChild(Role $child):void { 293 | $this->children[$child->getId()] = $child; 294 | } 295 | 296 | /** 297 | * @param Role $child 298 | * @internal Used for removing references to roles inheriting its permissions 299 | */ 300 | public function removeChild(Role $child):void { 301 | unset($this->children[$child->getId()]); 302 | } 303 | 304 | /** 305 | * @internal Used for getting roles inheriting its permissions 306 | */ 307 | public function getChildren(): array { 308 | return $this->children; 309 | } 310 | 311 | /** 312 | * @return Role[] 313 | */ 314 | public function getParents(): array { 315 | return $this->parents; 316 | } 317 | 318 | /** 319 | * @return bool[] 320 | */ 321 | public function getExtraPermissions(): array { 322 | return $this->extraPermissions; 323 | } 324 | 325 | private function recalculateCombinedPermissions(): void { 326 | $this->combinedPermissions = array_replace_recursive($this->getExtraPermissions(), $this->getPermissions()); 327 | } 328 | 329 | /** 330 | * @return bool[] 331 | */ 332 | public function getCombinedPermissions(): array { 333 | return $this->combinedPermissions; 334 | } 335 | 336 | /** 337 | * @return bool 338 | */ 339 | public function hasAllPermissions(): bool { 340 | return $this->hasAllPermissions; 341 | } 342 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/role/RoleManager.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\role; 31 | 32 | 33 | use CortexPE\Hierarchy\data\role\RoleDataSource; 34 | use CortexPE\Hierarchy\exception\HierarchyException; 35 | use CortexPE\Hierarchy\exception\RoleCollissionError; 36 | use CortexPE\Hierarchy\Hierarchy; 37 | use CortexPE\Hierarchy\member\OfflineMember; 38 | use RuntimeException; 39 | use function uasort; 40 | 41 | class RoleManager { 42 | /** @var Hierarchy */ 43 | protected $plugin; 44 | /** @var Role[] */ 45 | protected $roles = []; 46 | /** @var Role */ 47 | protected $defaultRole = null; 48 | /** @var RoleDataSource */ 49 | protected $dataSource; 50 | /** @var int */ 51 | protected $lastID = PHP_INT_MIN; 52 | /** @var array */ 53 | protected $lookupTable = []; // NAME => ID 54 | 55 | public function __construct(Hierarchy $plugin) { 56 | $this->plugin = $plugin; 57 | $this->dataSource = $plugin->getRoleDataSource(); 58 | } 59 | 60 | /** 61 | * @internal Used to load role data from a data source 62 | * 63 | * @param array $roles 64 | * 65 | * @throws HierarchyException 66 | */ 67 | public function loadRoles(array $roles): void { 68 | foreach($roles as $i => $roleData) { 69 | $role = new Role($this->plugin, $roleData["ID"], $roleData["Name"], [ 70 | "position" => $i, 71 | "isDefault" => $roleData["isDefault"] 72 | ]); 73 | foreach($roleData["Inherits"] ?? [] as $inherit){ 74 | if(!isset($this->lookupTable[$inherit])){ 75 | $this->plugin->getLogger()->warning("Cannot inherit role '$inherit' from '{$roleData['Name']}' ({$roleData['ID']}). Roles can only inherit permissions from other roles with lower positions."); 76 | continue; 77 | } 78 | $role->inheritRole($this->getRoleByName($inherit)); 79 | } 80 | $role->loadPermissions($roleData["Permissions"] ?? []); 81 | 82 | if($roleData["ID"] < 0) { 83 | throw new HierarchyException("Role '{$role->getName()}'({$role->getId()}) has a negative ID"); 84 | } 85 | if(!isset($this->roles[$roleData["ID"]])) { 86 | $this->roles[$roleData["ID"]] = $role; 87 | } else { 88 | throw new RoleCollissionError("Role '{$role->getName()}'({$role->getId()}) has a colliding ID"); 89 | } 90 | if($roleData["ID"] > $this->lastID) { 91 | $this->lastID = $roleData["ID"]; 92 | } 93 | if($roleData["isDefault"]) { 94 | if($i !== 0){ 95 | throw new HierarchyException("Default role must always be the first role (lowest position)."); 96 | } 97 | if($this->defaultRole === null) { 98 | $this->defaultRole = $role; 99 | } else { 100 | throw new RoleCollissionError("There can only be one default role"); 101 | } 102 | } 103 | $this->addToLookupTable($role); 104 | } 105 | if(!($this->defaultRole instanceof Role)) { 106 | throw new RuntimeException("No default role is set"); 107 | } 108 | $this->sortRoles(); 109 | $this->plugin->getLogger()->info("Loaded " . count($this->roles) . " roles"); 110 | } 111 | 112 | /** 113 | * Gets a role by its ID 114 | * 115 | * @param int $id 116 | * @return Role|null 117 | */ 118 | public function getRole(int $id): ?Role { 119 | return $this->roles[$id] ?? null; 120 | } 121 | 122 | /** 123 | * Adds a role to the lookup dictionary where Role Name => ID 124 | * 125 | * @param Role $role 126 | */ 127 | private function addToLookupTable(Role $role): void { 128 | $name = $role->getName(); 129 | $id = $role->getId(); 130 | if(isset($this->lookupTable[$name])) { 131 | $_id = $this->lookupTable[$name]; 132 | unset($this->lookupTable[$name]); 133 | $this->lookupTable["{$name}.{$_id}"] = $_id; 134 | $this->lookupTable["{$name}.{$id}"] = $role->getId(); 135 | } else { 136 | $this->lookupTable[$name] = $id; 137 | } 138 | $this->lookupTable[$id] = $id; 139 | } 140 | 141 | /** 142 | * Removes a role from the lookup dictionary where Role Name => ID 143 | * 144 | * @param Role $role 145 | */ 146 | private function removeFromLookupTable(Role $role): void { 147 | $name = $role->getName(); 148 | $id = $role->getId(); 149 | if(isset($this->lookupTable["{$name}.{$id}"]) && !isset($this->lookupTable[$name])){ 150 | unset($this->lookupTable["{$name}.{$id}"]); 151 | $this->lookupTable[$name] = $id; 152 | } else { 153 | unset($this->lookupTable[$name]); 154 | } 155 | unset($this->lookupTable[$id]); 156 | } 157 | 158 | /** 159 | * Tries to resolve a role by its name. 160 | * 161 | * WARNING: This uses the `roleName.ID` (MyRole.4) format for colliding role names 162 | * 163 | * @param string $roleName 164 | * 165 | * @return Role|null 166 | */ 167 | public function getRoleByName(string $roleName): ?Role { 168 | return $this->getRole($this->lookupTable[$roleName] ?? -1); 169 | } 170 | 171 | /** 172 | * @return Role 173 | */ 174 | public function getDefaultRole(): Role { 175 | return $this->defaultRole; 176 | } 177 | 178 | /** 179 | * Gets all roles in the server. 180 | * 181 | * @return Role[] 182 | */ 183 | public function getRoles(): array { 184 | return $this->roles; 185 | } 186 | 187 | /** 188 | * Creates a new role 189 | * 190 | * @param string $name 191 | * 192 | * @return Role 193 | */ 194 | public function createRole(string $name = "new role"): Role { 195 | $newRolePos = ($defRolePos = $this->defaultRole->getPosition()) + 1; 196 | foreach($this->roles as $role) { 197 | if($role->getPosition() > $defRolePos) { 198 | $role->bumpPosition(); 199 | } 200 | } 201 | $this->dataSource->createRoleOnStorage($name, $this->lastID += 1, $newRolePos); 202 | $role = $this->roles[$this->lastID] = new Role($this->plugin, $this->lastID, $name, [ 203 | "position" => $newRolePos 204 | ]); 205 | $this->sortRoles(); 206 | $this->addToLookupTable($role); 207 | 208 | return $role; 209 | } 210 | 211 | /** 212 | * Sorts roles by position (ascending) 213 | */ 214 | private function sortRoles(): void { 215 | uasort($this->roles, function (Role $a, Role $b): int { 216 | return $a->getPosition() <=> $b->getPosition(); 217 | }); 218 | } 219 | 220 | /** 221 | * Deletes a role, also deletes from the database. 222 | * 223 | * @param Role $role 224 | * @throws HierarchyException 225 | */ 226 | public function deleteRole(Role $role): void { 227 | if($role->isDefault()) { 228 | throw new RuntimeException("Default role cannot be deleted while at runtime"); 229 | } 230 | foreach($role->getChildren() as $child){ 231 | $child->unInheritRole($role); 232 | } 233 | $members = $role->getOnlineMembers(); 234 | foreach($members as $member) { 235 | $member->removeRole($role); 236 | } 237 | $role->getOfflineMembers(function(array $members) use ($role): void{ 238 | /** @var OfflineMember $member */ 239 | foreach($members as $member){ 240 | $member->removeRole($role, false); 241 | } 242 | unset($this->roles[$role->getId()]); 243 | $this->sortRoles(); 244 | $this->removeFromLookupTable($role); 245 | $this->dataSource->deleteRoleFromStorage($role); 246 | }); 247 | } 248 | } -------------------------------------------------------------------------------- /src/CortexPE/Hierarchy/task/InvalidRolePermissionCheckTask.php: -------------------------------------------------------------------------------- 1 | . 26 | */ 27 | 28 | declare(strict_types=1); 29 | 30 | namespace CortexPE\Hierarchy\task; 31 | 32 | 33 | use CortexPE\Hierarchy\Hierarchy; 34 | use CortexPE\Hierarchy\role\RoleManager; 35 | use pocketmine\permission\PermissionManager; 36 | use pocketmine\scheduler\Task; 37 | 38 | class InvalidRolePermissionCheckTask extends Task { 39 | /** @var Hierarchy */ 40 | private $plugin; 41 | /** @var RoleManager */ 42 | private $rMgr; 43 | 44 | public function __construct(Hierarchy $plugin) { 45 | $this->plugin = $plugin; 46 | $this->rMgr = $plugin->getRoleManager(); 47 | } 48 | 49 | public function onRun(int $currentTick) { 50 | foreach($this->plugin->getServer()->getPluginManager()->getPlugins() as $plugin){ 51 | if($plugin->isDisabled()){ 52 | $this->plugin->getLogger()->warning("Skipping permission existence check to avoid un-necessary console spam, please fix crashed plugins first."); 53 | return; 54 | } 55 | } 56 | $pMgr = PermissionManager::getInstance(); 57 | foreach($this->rMgr->getRoles() as $role){ 58 | $missing = []; 59 | foreach($role->getPermissions() as $permission => $val) { 60 | if($pMgr->getPermission($permission) === null) { 61 | $missing[] = $permission; 62 | $role->removePermissionInternal($permission); 63 | } 64 | } 65 | if(!empty($missing)) { 66 | $this->plugin->getLogger()->warning("Unknown permission nodes " . implode(", ", $missing) . " on " . $role->getName() . "(" . $role->getId() . ") role"); 67 | $role->updateMemberPermissions(); 68 | } 69 | } 70 | } 71 | } --------------------------------------------------------------------------------