mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00
506 lines
18 KiB
ObjectPascal
506 lines
18 KiB
ObjectPascal
unit types_helpers;
|
|
|
|
interface
|
|
|
|
uses
|
|
Vcl.Graphics;
|
|
|
|
type
|
|
|
|
// === Database ===
|
|
|
|
TNetType = (ntMySQL_TCPIP, ntMySQL_NamedPipe, ntMySQL_SSHtunnel,
|
|
ntMSSQL_NamedPipe, ntMSSQL_TCPIP, ntMSSQL_SPX, ntMSSQL_VINES, ntMSSQL_RPC,
|
|
ntPgSQL_TCPIP,
|
|
ntMariaDB_TCPIP, ntMariaDB_NamedPipe, ntMariaDB_SSHtunnel);
|
|
|
|
TNetTypeGroup = (ngMySQL, ngMSSQL, ngPgSQL, ngMariaDB);
|
|
|
|
// === Users and Privileges ===
|
|
|
|
TNodeObjectKind = (
|
|
okGlobal, okSchema, okGroupTables, okGroupViews, okGroupProcedures,
|
|
okGroupFunctions, okGroupTriggers, okGroupEvents, okTable, okView,
|
|
okProcedure, okFunction, {okTrigger, okEvent, }okColumn
|
|
);
|
|
TNodeObjectKinds = set of TNodeObjectKind;
|
|
|
|
TPrivilege = (prNone, prExecute, prProcess, prSelect, prShowDatabases,
|
|
prShowView, prAlter, prAlterRoutine, prCreate, prCreateRoutine,
|
|
prCreateTableSpace, prCreateTemporaryTables, prCreateView, prDelete, prDrop,
|
|
prEvent, prIndex, prInsert, prReferences, prTrigger, prUpdate, prCreateRole,
|
|
prCreateUser, prDropRole, prFile, prGrant, prLockTables, prReload,
|
|
prReplicationClient, prReplicationSlave, prShutdown, prSuper);
|
|
|
|
TPrivileges = set of TPrivilege;
|
|
|
|
TPrivilegeHelper = record helper for TPrivilege
|
|
public
|
|
function AvailableForObjectKind(const AObjectKind: TNodeObjectKind): Boolean;
|
|
function AvailableForServer(
|
|
const ANetTypeGroup: TNetTypeGroup;
|
|
const AVersion: Integer): Boolean;
|
|
function ColorBG: TColor;
|
|
function ColorFG: TColor;
|
|
function ToString: string;
|
|
function FromString(const AString: string): Boolean;
|
|
|
|
const
|
|
CL_VIOLET_FG = clPurple;
|
|
CL_GREEN_FG = clGreen;
|
|
CL_RED_FG = clMaroon;
|
|
CL_BLUE_FG = clNavy;
|
|
CL_VIOLET_BG = $00FFE1FF;
|
|
CL_GREEN_BG = $00E1FFE1;
|
|
CL_RED_BG = $00E1E1FF;
|
|
CL_BLUE_BG = $00FFE1E1;
|
|
end;
|
|
|
|
TPrivilegesHelper = record helper for TPrivileges
|
|
public
|
|
function ToString: string;
|
|
function FromGrant(const AGrant: string): Boolean;
|
|
function FromString(const AString: string): Boolean;
|
|
end;
|
|
|
|
TUserKind = (ukGroupUsers, ukGroupRoles, ukUser, ukRole, ukHost);
|
|
TUserChangedPart = (ucpCaption, ucpProperties, ucpPrivileges);
|
|
TUserChangedParts = set of TUserChangedPart;
|
|
TUserProblem = (upNone, upEmptyPassword, upInvalidPasswordLen, upSkipNameResolve, upUnknown);
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
apphelpers, SynRegExpr, System.Classes, System.SysUtils;
|
|
|
|
const
|
|
S_REGEX_GRANTS = '^GRANT\s+(.+)\s+ON\s+((TABLE|FUNCTION|PROCEDURE)\s+)?(\*|[`"]([^`"]+)[`"])\.(\*|[`"]([^`"]+)[`"])\s+TO\s+\S+(\s+IDENTIFIED\s+BY\s+(PASSWORD)?\s+''?([^'']+)''?)?(\s+.+)?$';
|
|
|
|
{ TPrivilegeHelper }
|
|
|
|
function TPrivilegeHelper.AvailableForObjectKind(
|
|
const AObjectKind: TNodeObjectKind): Boolean;
|
|
begin
|
|
Result:= FALSE;
|
|
case Self of
|
|
prAlter:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable];
|
|
prAlterRoutine:
|
|
Result := AObjectKind in [okGlobal, okSchema, okProcedure, okFunction];
|
|
prCreate:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable];
|
|
prCreateRole:
|
|
Result := AObjectKind in [okGlobal];
|
|
prCreateRoutine:
|
|
Result := AObjectKind in [okGlobal, okSchema, okProcedure, okFunction];
|
|
prCreateTableSpace:
|
|
Result := AObjectKind in [okGlobal];
|
|
prCreateTemporaryTables:
|
|
Result := AObjectKind in [okGlobal, okSchema];
|
|
prCreateUser:
|
|
Result := AObjectKind in [okGlobal];
|
|
prCreateView:
|
|
Result := AObjectKind in [okGlobal, okSchema, okView];
|
|
prDelete:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable];
|
|
prDrop:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable, okView];
|
|
prDropRole:
|
|
Result := AObjectKind in [okGlobal];
|
|
prEvent:
|
|
Result := AObjectKind in [okGlobal, okSchema];
|
|
prExecute:
|
|
Result := AObjectKind in [okGlobal, okSchema, okProcedure, okFunction];
|
|
prFile:
|
|
Result := AObjectKind in [okGlobal];
|
|
prGrant:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable, okProcedure,
|
|
okFunction, okColumn];
|
|
prIndex:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable];
|
|
prInsert:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable, okColumn];
|
|
prLockTables:
|
|
Result := AObjectKind in [okGlobal, okSchema];
|
|
prProcess:
|
|
Result := AObjectKind in [okGlobal];
|
|
prReferences:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable];
|
|
prReload:
|
|
Result := AObjectKind in [okGlobal];
|
|
prReplicationClient:
|
|
Result := AObjectKind in [okGlobal];
|
|
prReplicationSlave:
|
|
Result := AObjectKind in [okGlobal];
|
|
prSelect:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable, okColumn];
|
|
prShowDatabases:
|
|
Result := AObjectKind in [okGlobal];
|
|
prShowView:
|
|
Result := AObjectKind in [okGlobal, okSchema, okView];
|
|
prShutdown:
|
|
Result := AObjectKind in [okGlobal];
|
|
prSuper:
|
|
Result := AObjectKind in [okGlobal];
|
|
prTrigger:
|
|
Result := AObjectKind in [okGlobal, okSchema];
|
|
prUpdate:
|
|
Result := AObjectKind in [okGlobal, okSchema, okTable, okColumn];
|
|
end;
|
|
end;
|
|
|
|
function TPrivilegeHelper.AvailableForServer(const ANetTypeGroup: TNetTypeGroup;
|
|
const AVersion: Integer): Boolean;
|
|
var
|
|
NTG: TNetTypeGroup;
|
|
V: Integer;
|
|
begin
|
|
Result:= FALSE;
|
|
NTG:= ANetTypeGroup;
|
|
V:= AVersion;
|
|
case Self of
|
|
// TODO: Add other supported servers and their min versions!
|
|
|
|
prAlter:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prAlterRoutine:
|
|
Result := ((NTG = ngMySQL) and (V >= 50003)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prCreate:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prCreateRole:
|
|
Result := ((NTG = ngMySQL) and (V >= 80000)) or
|
|
((NTG = ngMariaDB) and (FALSE)); // Not supported in MariaDB! Here we have to have CREATE USER privilege.
|
|
prCreateRoutine:
|
|
Result := ((NTG = ngMySQL) and (V >= 50003)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prCreateTableSpace:
|
|
Result := ((NTG = ngMySQL) and (V >= 50404)) or
|
|
((NTG = ngMariaDB) and (FALSE));
|
|
prCreateTemporaryTables:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prCreateUser:
|
|
Result := ((NTG = ngMySQL) and (V >= 50003)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prCreateView:
|
|
Result := ((NTG = ngMySQL) and (V >= 50001)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prDelete:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prDrop:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prDropRole:
|
|
Result := ((NTG = ngMySQL) and (V >= 80000)) or
|
|
((NTG = ngMariaDB) and (FALSE)); // Not supported in MariaDB! Here we have to have CREATE USER privilege.
|
|
prEvent:
|
|
Result := ((NTG = ngMySQL) and (V >= 50106)) or
|
|
((NTG = ngMariaDB) and (V >= 50106));
|
|
prExecute:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prFile:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prGrant:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prIndex:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prInsert:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prLockTables:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prProcess:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prReferences:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prReload:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prReplicationClient:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prReplicationSlave:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prSelect:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prShowDatabases:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prShowView:
|
|
Result := ((NTG = ngMySQL) and (V >= 50001)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prShutdown:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prSuper:
|
|
Result := ((NTG = ngMySQL) and (V >= 40002)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
prTrigger:
|
|
Result := ((NTG = ngMySQL) and (V >= 50106)) or
|
|
((NTG = ngMariaDB) and (V >= 50106));
|
|
prUpdate:
|
|
Result := ((NTG = ngMySQL) and (V >= 0)) or
|
|
((NTG = ngMariaDB) and (V >= 50100));
|
|
end;
|
|
end;
|
|
|
|
function TPrivilegeHelper.ColorBG: TColor;
|
|
begin
|
|
Result:= clNone;
|
|
case Self of
|
|
prExecute: Result:= CL_GREEN_BG;
|
|
prProcess: Result:= CL_GREEN_BG;
|
|
prSelect: Result:= CL_GREEN_BG;
|
|
prShowDatabases: Result:= CL_GREEN_BG;
|
|
prShowView: Result:= CL_GREEN_BG;
|
|
prAlter: Result:= CL_RED_BG;
|
|
prAlterRoutine: Result:= CL_RED_BG;
|
|
prCreate: Result:= CL_RED_BG;
|
|
prCreateRoutine: Result:= CL_RED_BG;
|
|
prCreateTableSpace: Result:= CL_RED_BG;
|
|
prCreateTemporaryTables: Result:= CL_RED_BG;
|
|
prCreateView: Result:= CL_RED_BG;
|
|
prDelete: Result:= CL_RED_BG;
|
|
prDrop: Result:= CL_RED_BG;
|
|
prEvent: Result:= CL_RED_BG;
|
|
prIndex: Result:= CL_RED_BG;
|
|
prInsert: Result:= CL_RED_BG;
|
|
prReferences: Result:= CL_RED_BG;
|
|
prTrigger: Result:= CL_RED_BG;
|
|
prUpdate: Result:= CL_RED_BG;
|
|
prCreateRole: Result:= CL_BLUE_BG;
|
|
prCreateUser: Result:= CL_BLUE_BG;
|
|
prDropRole: Result:= CL_BLUE_BG;
|
|
prFile: Result:= CL_BLUE_BG;
|
|
prGrant: Result:= CL_BLUE_BG;
|
|
prLockTables: Result:= CL_BLUE_BG;
|
|
prReload: Result:= CL_BLUE_BG;
|
|
prReplicationClient: Result:= CL_BLUE_BG;
|
|
prReplicationSlave: Result:= CL_BLUE_BG;
|
|
prShutdown: Result:= CL_BLUE_BG;
|
|
prSuper: Result:= CL_BLUE_BG;
|
|
end;
|
|
end;
|
|
|
|
function TPrivilegeHelper.ColorFG: TColor;
|
|
begin
|
|
Result:= clNone;
|
|
case Self of
|
|
prExecute: Result:= CL_GREEN_FG;
|
|
prProcess: Result:= CL_GREEN_FG;
|
|
prSelect: Result:= CL_GREEN_FG;
|
|
prShowDatabases: Result:= CL_GREEN_FG;
|
|
prShowView: Result:= CL_GREEN_FG;
|
|
prAlter: Result:= CL_RED_FG;
|
|
prAlterRoutine: Result:= CL_RED_FG;
|
|
prCreate: Result:= CL_RED_FG;
|
|
prCreateRoutine: Result:= CL_RED_FG;
|
|
prCreateTableSpace: Result:= CL_RED_FG;
|
|
prCreateTemporaryTables: Result:= CL_RED_FG;
|
|
prCreateView: Result:= CL_RED_FG;
|
|
prDelete: Result:= CL_RED_FG;
|
|
prDrop: Result:= CL_RED_FG;
|
|
prEvent: Result:= CL_RED_FG;
|
|
prIndex: Result:= CL_RED_FG;
|
|
prInsert: Result:= CL_RED_FG;
|
|
prReferences: Result:= CL_RED_FG;
|
|
prTrigger: Result:= CL_RED_FG;
|
|
prUpdate: Result:= CL_RED_FG;
|
|
prCreateRole: Result:= CL_BLUE_FG;
|
|
prCreateUser: Result:= CL_BLUE_FG;
|
|
prDropRole: Result:= CL_BLUE_FG;
|
|
prFile: Result:= CL_BLUE_FG;
|
|
prGrant: Result:= CL_BLUE_FG;
|
|
prLockTables: Result:= CL_BLUE_FG;
|
|
prReload: Result:= CL_BLUE_FG;
|
|
prReplicationClient: Result:= CL_BLUE_FG;
|
|
prReplicationSlave: Result:= CL_BLUE_FG;
|
|
prShutdown: Result:= CL_BLUE_FG;
|
|
prSuper: Result:= CL_BLUE_FG;
|
|
end;
|
|
end;
|
|
|
|
function TPrivilegeHelper.FromString(const AString: string): Boolean;
|
|
begin
|
|
Self:= prNone;
|
|
if CompareText(AString, 'ALTER') = 0 then
|
|
Self:= prAlter
|
|
else if CompareText(AString, 'ALTER ROUTINE') = 0 then
|
|
Self:= prAlterRoutine
|
|
else if CompareText(AString, 'CREATE') = 0 then
|
|
Self:= prCreate
|
|
else if CompareText(AString, 'CREATE ROLE') = 0 then
|
|
Self:= prCreateRole
|
|
else if CompareText(AString, 'CREATE ROUTINE') = 0 then
|
|
Self:= prCreateRoutine
|
|
else if CompareText(AString, 'CREATE TABLESPACE') = 0 then
|
|
Self:= prCreateTableSpace
|
|
else if CompareText(AString, 'CREATE TEMPORARY TABLES') = 0 then
|
|
Self:= prCreateTemporaryTables
|
|
else if CompareText(AString, 'CREATE USER') = 0 then
|
|
Self:= prCreateUser
|
|
else if CompareText(AString, 'CREATE VIEW') = 0 then
|
|
Self:= prCreateView
|
|
else if CompareText(AString, 'DELETE') = 0 then
|
|
Self:= prDelete
|
|
else if CompareText(AString, 'DROP') = 0 then
|
|
Self:= prDrop
|
|
else if CompareText(AString, 'DROP ROLE') = 0 then
|
|
Self:= prDropRole
|
|
else if CompareText(AString, 'EVENT') = 0 then
|
|
Self:= prEvent
|
|
else if CompareText(AString, 'EXECUTE') = 0 then
|
|
Self:= prExecute
|
|
else if CompareText(AString, 'FILE') = 0 then
|
|
Self:= prFile
|
|
else if CompareText(AString, 'GRANT') = 0 then
|
|
Self:= prGrant
|
|
else if CompareText(AString, 'INDEX') = 0 then
|
|
Self:= prIndex
|
|
else if CompareText(AString, 'INSERT') = 0 then
|
|
Self:= prInsert
|
|
else if CompareText(AString, 'LOCK TABLES') = 0 then
|
|
Self:= prLockTables
|
|
else if CompareText(AString, 'PROCESS') = 0 then
|
|
Self:= prProcess
|
|
else if CompareText(AString, 'REFERENCES') = 0 then
|
|
Self:= prReferences
|
|
else if CompareText(AString, 'RELOAD') = 0 then
|
|
Self:= prReload
|
|
else if CompareText(AString, 'REPLICATION CLIENT') = 0 then
|
|
Self:= prReplicationClient
|
|
else if CompareText(AString, 'REPLICATION SLAVE') = 0 then
|
|
Self:= prReplicationSlave
|
|
else if CompareText(AString, 'SELECT') = 0 then
|
|
Self:= prSelect
|
|
else if CompareText(AString, 'SHOW DATABASES') = 0 then
|
|
Self:= prShowDatabases
|
|
else if CompareText(AString, 'SHOW VIEW') = 0 then
|
|
Self:= prShowView
|
|
else if CompareText(AString, 'SHUTDOWN') = 0 then
|
|
Self:= prShutdown
|
|
else if CompareText(AString, 'SUPER') = 0 then
|
|
Self:= prSuper
|
|
else if CompareText(AString, 'TRIGGER') = 0 then
|
|
Self:= prTrigger
|
|
else if CompareText(AString, 'UPDATE') = 0 then
|
|
Self:= prUpdate;
|
|
Result:= (Self <> prNone);
|
|
end;
|
|
|
|
function TPrivilegeHelper.ToString: string;
|
|
begin
|
|
case Self of
|
|
prAlter: Result:= 'ALTER';
|
|
prAlterRoutine: Result:= 'ALTER ROUTINE';
|
|
prCreate: Result:= 'CREATE';
|
|
prCreateRole: Result:= 'CREATE ROLE';
|
|
prCreateRoutine: Result:= 'CREATE ROUTINE';
|
|
prCreateTableSpace: Result:= 'CREATE TABLE SPACE';
|
|
prCreateTemporaryTables: Result:= 'CREATE TEMPORARY TABLES';
|
|
prCreateUser: Result:= 'CREATE USER';
|
|
prCreateView: Result:= 'CREATE VIEW';
|
|
prDelete: Result:= 'DELETE';
|
|
prDrop: Result:= 'DROP';
|
|
prDropRole: Result:= 'DROP ROLE';
|
|
prEvent: Result:= 'EVENT';
|
|
prExecute: Result:= 'EXECUTE';
|
|
prFile: Result:= 'FILE';
|
|
prGrant: Result:= 'GRANT';
|
|
prIndex: Result:= 'INDEX';
|
|
prInsert: Result:= 'INSERT';
|
|
prLockTables: Result:= 'LOCK TABLES';
|
|
prProcess: Result:= 'PROCESS';
|
|
prReferences: Result:= 'REFERENCES';
|
|
prReload: Result:= 'RELOAD';
|
|
prReplicationClient: Result:= 'REPLICATION CLIENT';
|
|
prReplicationSlave: Result:= 'REPLICATION SLAVE';
|
|
prSelect: Result:= 'SELECT';
|
|
prShowDatabases: Result:= 'SHOW DATABASES';
|
|
prShowView: Result:= 'SHOW VIEW';
|
|
prShutdown: Result:= 'SHUTDOWN';
|
|
prSuper: Result:= 'SUPER';
|
|
prTrigger: Result:= 'TRIGGER';
|
|
prUpdate: Result:= 'UPDATE';
|
|
end;
|
|
end;
|
|
|
|
{ TPrivilegesHelper }
|
|
|
|
function TPrivilegesHelper.FromGrant(const AGrant: string): Boolean;
|
|
var
|
|
R: TRegExpr;
|
|
S: string;
|
|
begin
|
|
Result:= FALSE;
|
|
R := TRegExpr.Create;
|
|
try
|
|
R.ModifierI:= True;
|
|
R.Expression:= S_REGEX_GRANTS;
|
|
if R.Exec(AGrant) then begin
|
|
S:= R.Match[1];
|
|
if S.Length > 0 then begin
|
|
Result:= Self.FromString(S);
|
|
end;
|
|
end;
|
|
finally
|
|
FreeAndNil(R);
|
|
end;
|
|
end;
|
|
|
|
function TPrivilegesHelper.FromString(const AString: string): Boolean;
|
|
var
|
|
P: TPrivilege;
|
|
S: string;
|
|
SL: TStringList;
|
|
begin
|
|
Result:= FALSE;
|
|
if CompareText(AString.Trim, 'ALL PRIVILEGES') = 0 then begin
|
|
for P:= Low(TPrivilege) to High(TPrivilege) do begin
|
|
Self:= Self + [P] - [prNone];
|
|
end;
|
|
Result:= TRUE;
|
|
end else begin
|
|
SL:= Explode(',', AString);
|
|
try
|
|
for S in SL do begin
|
|
if P.FromString(S.Trim) then begin
|
|
Self:= Self + [P] - [prNone];
|
|
end;
|
|
end;
|
|
Result:= (SL.Count > 0);
|
|
finally
|
|
FreeAndNil(SL);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPrivilegesHelper.ToString: string;
|
|
var
|
|
P: TPrivilege;
|
|
S: string;
|
|
begin
|
|
Result:= '';
|
|
S:= '';
|
|
for P in Self do begin
|
|
S:= S + ',' + P.ToString;
|
|
end;
|
|
Result:= Copy(S, 2, Length(S));
|
|
end;
|
|
|
|
end.
|