+Required. RT's core role types are C<Requestor>, C<Cc>, C<AdminCc>, and
+C<Owner>. Extensions may add their own.
+
+=item Object
+
+Optional. The object on which this role applies, used to set Domain and
+Instance automatically.
+
+=item Domain
+
+Optional. The class on which this role applies, with C<-Role> appended. RT's
+supported core role group domains are C<RT::Ticket-Role>, C<RT::Queue-Role>,
+and C<RT::System-Role>.
+
+Not required if you pass an Object.
+
+=item Instance
+
+Optional. The numeric ID of the object (of the class encoded in Domain) on
+which this role applies. If Domain is C<RT::System-Role>, Instance should be C<1>.
+
+Not required if you pass an Object.
+
+=item InsideTransaction
+
+Optional. Defaults to true in expectation of usual call sites. If you call
+this method while not inside a transaction, you C<MUST> pass a false value for
+this parameter.
+
+=back
+
+You must pass either an Object or both Domain and Instance.
+
+Returns a tuple of (id, Message). If id is false, the create failed and
+Message should contain an error string.
+
+=cut
+
+sub CreateRoleGroup {
+ my $self = shift;
+ my %args = ( Instance => undef,
+ Name => undef,
+ Domain => undef,
+ Object => undef,
+ InsideTransaction => 1,
+ @_ );
+
+ # Translate Object to Domain + Instance
+ my $object = delete $args{Object};
+ if ( $object ) {
+ $args{Domain} = ref($object) . "-Role";
+ $args{Instance} = $object->id;
+ }
+
+ unless ($args{Instance}) {
+ return ( 0, $self->loc("An Instance must be provided") );
+ }
+
+ unless ($self->ValidateRoleGroup(%args)) {
+ return ( 0, $self->loc("Invalid Group Name and Domain") );
+ }
+
+ if ( exists $args{'Type'} ) {
+ RT->Deprecated( Instead => 'Name', Arguments => 'Type', Remove => '4.4' );
+ $args{'Name'} = $args{'Type'};
+ }
+
+ my %create = map { $_ => $args{$_} } qw(Domain Instance Name);
+
+ my $duplicate = RT::Group->new( RT->SystemUser );
+ $duplicate->LoadByCols( %create );
+ if ($duplicate->id) {
+ return ( 0, $self->loc("Role group exists already") );
+ }
+
+ my ($id, $msg) = $self->_Create(
+ InsideTransaction => $args{InsideTransaction},
+ %create,
+ );
+
+ if ($self->SingleMemberRoleGroup) {
+ $self->_AddMember(
+ PrincipalId => RT->Nobody->Id,
+ InsideTransaction => $args{InsideTransaction},
+ RecordTransaction => 0,
+ Object => $object,
+ );
+ }
+
+ return ($id, $msg);
+}
+
+sub RoleClass {
+ my $self = shift;
+ my $domain = shift || $self->Domain;
+ return unless $domain =~ /^(.+)-Role$/;
+ return unless $1->DOES("RT::Record::Role::Roles");
+ return $1;
+}
+
+=head2 ValidateRoleGroup
+
+Takes a param hash containing Domain and Type which are expected to be values
+passed into L</CreateRoleGroup>. Returns true if the specified Type is a
+registered role on the specified Domain. Otherwise returns false.
+
+=cut
+
+sub ValidateRoleGroup {
+ my $self = shift;
+ my %args = (@_);
+ return 0 unless $args{Domain} and ($args{Type} or $args{'Name'});
+
+ my $class = $self->RoleClass($args{Domain});
+ return 0 unless $class;
+
+ return $class->HasRole($args{Type}||$args{'Name'});
+}
+
+=head2 SingleMemberRoleGroup
+
+=cut
+
+sub SingleMemberRoleGroup {
+ my $self = shift;
+ my $class = $self->RoleClass;
+ return unless $class;
+ return $class->Role($self->Name)->{Single};
+}
+
+sub SingleMemberRoleGroupColumn {
+ my $self = shift;
+ my ($class) = $self->Domain =~ /^(.+)-Role$/;
+ return unless $class;
+
+ my $role = $class->Role($self->Name);
+ return unless $role->{Class} eq $class;
+ return $role->{Column};
+}
+
+sub RoleGroupObject {
+ my $self = shift;
+ my ($class) = $self->Domain =~ /^(.+)-Role$/;
+ return unless $class;
+ my $obj = $class->new( $self->CurrentUser );
+ $obj->Load( $self->Instance );
+ return $obj;
+}
+
+sub Type {
+ my $self = shift;
+ RT->Deprecated( Instead => 'Name', Remove => '4.4' );
+ return $self->_Value('Type', @_);
+}
+
+sub SetType {
+ my $self = shift;
+ RT->Deprecated( Instead => 'Name', Remove => '4.4' );
+ return $self->SetName(@_);
+}
+
+sub SetName {
+ my $self = shift;
+ my $value = shift;
+
+ my ($status, $msg) = $self->_Set( Field => 'Name', Value => $value );
+ return ($status, $msg) unless $status;
+
+ {
+ my ($status, $msg) = $self->__Set( Field => 'Type', Value => $value );
+ RT->Logger->error("Couldn't set Type: $msg") unless $status;
+ }
+
+ return ($status, $msg);
+}
+
+=head2 Delete
+
+Delete this object
+
+=cut
+
+sub Delete {
+ my $self = shift;
+
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ return ( 0, 'Permission Denied' );
+ }
+
+ $RT::Logger->crit("Deleting groups violates referential integrity until we go through and fix this");
+ # TODO XXX
+
+ # Remove the principal object
+ # Remove this group from anything it's a member of.
+ # Remove all cached members of this group
+ # Remove any rights granted to this group
+ # remove any rights delegated by way of this group
+
+ return ( $self->SUPER::Delete(@_) );
+}
+
+
+=head2 SetDisabled BOOL
+
+If passed a positive value, this group will be disabled. No rights it commutes or grants will be honored.
+It will not appear in most group listings.
+
+This routine finds all the cached group members that are members of this group (recursively) and disables them.
+
+=cut
+
+ # }}}
+
+ sub SetDisabled {
+ my $self = shift;
+ my $val = shift;
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ return (0, $self->loc('Permission Denied'));
+ }
+ $RT::Handle->BeginTransaction();
+ $self->PrincipalObj->SetDisabled($val);
+
+
+
+
+ # Find all occurrences of this member as a member of this group
+ # in the cache and nuke them, recursively.
+
+ # The following code will delete all Cached Group members
+ # where this member's group is _not_ the primary group
+ # (Ie if we're deleting C as a member of B, and B happens to be
+ # a member of A, will delete C as a member of A without touching
+ # C as a member of B
+
+ my $cached_submembers = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ $cached_submembers->Limit( FIELD => 'ImmediateParentId', OPERATOR => '=', VALUE => $self->Id);
+
+ #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space.
+ # TODO what about the groups key cache?
+ RT::Principal->InvalidateACLCache();
+
+
+
+ while ( my $item = $cached_submembers->Next() ) {
+ my $del_err = $item->SetDisabled($val);
+ unless ($del_err) {
+ $RT::Handle->Rollback();
+ $RT::Logger->warning("Couldn't disable cached group submember ".$item->Id);
+ return (undef);
+ }
+ }
+
+ $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
+
+ $RT::Handle->Commit();
+ if ( $val == 1 ) {
+ return (1, $self->loc("Group disabled"));
+ } else {
+ return (1, $self->loc("Group enabled"));
+ }
+
+}
+
+
+
+
+sub Disabled {
+ my $self = shift;
+ $self->PrincipalObj->Disabled(@_);
+}
+
+
+
+=head2 DeepMembersObj
+
+Returns an RT::CachedGroupMembers object of this group's members,
+including all members of subgroups.
+
+=cut
+
+sub DeepMembersObj {
+ my $self = shift;
+ my $members_obj = RT::CachedGroupMembers->new( $self->CurrentUser );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+ $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
+
+ return ( $members_obj );
+
+}
+
+
+
+=head2 MembersObj
+
+Returns an RT::GroupMembers object of this group's direct members.
+
+=cut
+
+sub MembersObj {
+ my $self = shift;
+ my $members_obj = RT::GroupMembers->new( $self->CurrentUser );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+ $members_obj->LimitToMembersOfGroup( $self->PrincipalId );
+
+ return ( $members_obj );
+
+}
+
+
+
+=head2 GroupMembersObj [Recursively => 1]
+
+Returns an L<RT::Groups> object of this group's members.
+By default returns groups including all subgroups, but
+could be changed with C<Recursively> named argument.
+
+B<Note> that groups are not filtered by type and result
+may contain as well system groups and others.
+
+=cut
+
+sub GroupMembersObj {
+ my $self = shift;
+ my %args = ( Recursively => 1, @_ );
+
+ my $groups = RT::Groups->new( $self->CurrentUser );
+ my $members_table = $args{'Recursively'}?
+ 'CachedGroupMembers': 'GroupMembers';
+
+ my $members_alias = $groups->NewAlias( $members_table );
+ $groups->Join(
+ ALIAS1 => $members_alias, FIELD1 => 'MemberId',
+ ALIAS2 => $groups->PrincipalsAlias, FIELD2 => 'id',
+ );
+ $groups->Limit(
+ ALIAS => $members_alias,
+ FIELD => 'GroupId',
+ VALUE => $self->PrincipalId,
+ );
+ $groups->Limit(
+ ALIAS => $members_alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ ) if $args{'Recursively'};
+
+ return $groups;
+}
+
+
+
+=head2 UserMembersObj
+
+Returns an L<RT::Users> object of this group's members, by default
+returns users including all members of subgroups, but could be
+changed with C<Recursively> named argument.
+
+=cut
+
+sub UserMembersObj {
+ my $self = shift;
+ my %args = ( Recursively => 1, @_ );
+
+ #If we don't have rights, don't include any results
+ # TODO XXX WHY IS THERE NO ACL CHECK HERE?
+
+ my $members_table = $args{'Recursively'}?
+ 'CachedGroupMembers': 'GroupMembers';
+
+ my $users = RT::Users->new($self->CurrentUser);
+ my $members_alias = $users->NewAlias( $members_table );
+ $users->Join(
+ ALIAS1 => $members_alias, FIELD1 => 'MemberId',
+ ALIAS2 => $users->PrincipalsAlias, FIELD2 => 'id',
+ );
+ $users->Limit(
+ ALIAS => $members_alias,
+ FIELD => 'GroupId',
+ VALUE => $self->PrincipalId,
+ );
+ $users->Limit(
+ ALIAS => $members_alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ ) if $args{'Recursively'};
+
+ return ( $users);
+}
+
+
+
+=head2 MemberEmailAddresses
+
+Returns an array of the email addresses of all of this group's members
+
+
+=cut
+
+sub MemberEmailAddresses {
+ my $self = shift;
+ return sort grep defined && length,
+ map $_->EmailAddress,
+ @{ $self->UserMembersObj->ItemsArrayRef };
+}
+
+
+
+=head2 MemberEmailAddressesAsString
+
+Returns a comma delimited string of the email addresses of all users
+who are members of this group.
+
+=cut
+
+
+sub MemberEmailAddressesAsString {
+ my $self = shift;
+ return (join(', ', $self->MemberEmailAddresses));
+}
+
+
+
+=head2 AddMember PRINCIPAL_ID
+
+AddMember adds a principal to this group. It takes a single principal id.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+=cut
+
+sub AddMember {
+ my $self = shift;
+ my $new_member = shift;
+
+
+
+ # We should only allow membership changes if the user has the right
+ # to modify group membership or the user is the principal in question
+ # and the user has the right to modify his own membership
+ unless ( ($new_member == $self->CurrentUser->PrincipalId &&
+ $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
+ $self->CurrentUserHasRight('AdminGroupMembership') ) {
+ #User has no permission to be doing this
+ return ( 0, $self->loc("Permission Denied") );
+ }
+
+ $self->_AddMember(PrincipalId => $new_member);
+}
+
+# A helper subroutine for AddMember that bypasses the ACL checks
+# this should _ONLY_ ever be called from Ticket/Queue AddWatcher
+# when we want to deal with groups according to queue rights
+# In the dim future, this will all get factored out and life
+# will get better
+
+# takes a paramhash of { PrincipalId => undef, InsideTransaction }
+
+sub _AddMember {
+ my $self = shift;
+ my %args = ( PrincipalId => undef,
+ InsideTransaction => undef,
+ RecordTransaction => 1,
+ @_);
+
+ # RecordSetTransaction is used by _DeleteMember to get one txn but not the other
+ $args{RecordSetTransaction} = $args{RecordTransaction}
+ unless exists $args{RecordSetTransaction};
+
+ my $new_member = $args{'PrincipalId'};
+
+ unless ($self->Id) {
+ $RT::Logger->crit("Attempting to add a member to a group which wasn't loaded. 'oops'");
+ return(0, $self->loc("Group not found"));
+ }
+
+ unless ($new_member =~ /^\d+$/) {
+ $RT::Logger->crit("_AddMember called with a parameter that's not an integer.");
+ }
+
+
+ my $new_member_obj = RT::Principal->new( $self->CurrentUser );
+ $new_member_obj->Load($new_member);
+
+
+ unless ( $new_member_obj->Id ) {
+ $RT::Logger->debug("Couldn't find that principal");
+ return ( 0, $self->loc("Couldn't find that principal") );
+ }
+
+ if ( $self->HasMember( $new_member_obj ) ) {
+
+ #User is already a member of this group. no need to add it
+ return ( 0, $self->loc("Group already has member: [_1]", $new_member_obj->Object->Name) );
+ }
+ if ( $new_member_obj->IsGroup &&
+ $new_member_obj->Object->HasMemberRecursively($self->PrincipalObj) ) {
+
+ #This group can't be made to be a member of itself
+ return ( 0, $self->loc("Groups can't be members of their members"));
+ }
+
+ my @purge;
+ push @purge, @{$self->MembersObj->ItemsArrayRef}
+ if $self->SingleMemberRoleGroup;
+
+ my $member_object = RT::GroupMember->new( $self->CurrentUser );
+ my $id = $member_object->Create(
+ Member => $new_member_obj,
+ Group => $self->PrincipalObj,
+ InsideTransaction => $args{'InsideTransaction'}
+ );
+
+ return(0, $self->loc("Couldn't add member to group"))
+ unless $id;
+
+ # Purge all previous members (we're a single member role group)
+ my $old_member_id;
+ for my $member (@purge) {
+ my $old_member = $member->MemberId;
+ my ($ok, $msg) = $member->Delete();
+ return(0, $self->loc("Couldn't remove previous member: [_1]", $msg))
+ unless $ok;
+
+ # We remove all members in this loop, but there should only ever be one
+ # member. Keep track of the last one successfully removed for the
+ # SetWatcher transaction below.
+ $old_member_id = $old_member;
+ }
+
+ # Update the column
+ if (my $col = $self->SingleMemberRoleGroupColumn) {
+ my $obj = $args{Object} || $self->RoleGroupObject;
+ my ($ok, $msg) = $obj->_Set(
+ Field => $col,
+ Value => $new_member_obj->Id,
+ CheckACL => 0, # don't check acl
+ RecordTransaction => $args{'RecordSetTransaction'},
+ );
+ return (0, $self->loc("Could not update column [_1]: [_2]", $col, $msg))
+ unless $ok;
+ }
+
+ # Record transactions for UserDefined groups
+ if ($args{RecordTransaction} && $self->Domain eq 'UserDefined') {
+ $new_member_obj->Object->_NewTransaction(
+ Type => 'AddMembership',
+ Field => $self->PrincipalObj->id,
+ );
+
+ $self->_NewTransaction(
+ Type => 'AddMember',
+ Field => $new_member,
+ );
+ }
+
+ # Record an Add/SetWatcher txn on the object if we're a role group
+ if ($args{RecordTransaction} and $self->RoleClass) {
+ my $obj = $args{Object} || $self->RoleGroupObject;
+
+ if ($self->SingleMemberRoleGroup) {
+ $obj->_NewTransaction(
+ Type => 'SetWatcher',
+ OldValue => $old_member_id,
+ NewValue => $new_member_obj->Id,
+ Field => $self->Name,
+ );
+ } else {
+ $obj->_NewTransaction(
+ Type => 'AddWatcher', # use "watcher" for history's sake
+ NewValue => $new_member_obj->Id,
+ Field => $self->Name,
+ );
+ }
+ }
+
+ return (1, $self->loc("[_1] set to [_2]",
+ $self->loc($self->Name), $new_member_obj->Object->Name) )
+ if $self->SingleMemberRoleGroup;
+
+ return ( 1, $self->loc("Member added: [_1]", $new_member_obj->Object->Name) );
+}
+
+
+=head2 HasMember RT::Principal|id
+
+Takes an L<RT::Principal> object or its id returns a GroupMember Id if that user is a
+member of this group.
+Returns undef if the user isn't a member of the group or if the current
+user doesn't have permission to find out. Arguably, it should differentiate
+between ACL failure and non membership.
+
+=cut
+
+sub HasMember {
+ my $self = shift;
+ my $principal = shift;
+
+ my $id;
+ if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
+ $id = $principal->id;
+ } elsif ( $principal =~ /^\d+$/ ) {
+ $id = $principal;
+ } else {
+ $RT::Logger->error("Group::HasMember was called with an argument that".
+ " isn't an RT::Principal or id. It's ".($principal||'(undefined)'));
+ return(undef);
+ }
+ return undef unless $id;
+
+ my $member_obj = RT::GroupMember->new( $self->CurrentUser );
+ $member_obj->LoadByCols(
+ MemberId => $id,
+ GroupId => $self->PrincipalId
+ );
+
+ if ( my $member_id = $member_obj->id ) {
+ return $member_id;
+ }
+ else {
+ return (undef);
+ }
+}
+
+
+
+=head2 HasMemberRecursively RT::Principal|id
+
+Takes an L<RT::Principal> object or its id and returns true if that user is a member of
+this group.
+Returns undef if the user isn't a member of the group or if the current
+user doesn't have permission to find out. Arguably, it should differentiate
+between ACL failure and non membership.
+
+=cut
+
+sub HasMemberRecursively {
+ my $self = shift;
+ my $principal = shift;
+
+ my $id;
+ if ( UNIVERSAL::isa($principal,'RT::Principal') ) {
+ $id = $principal->id;
+ } elsif ( $principal =~ /^\d+$/ ) {
+ $id = $principal;
+ } else {
+ $RT::Logger->error("Group::HasMemberRecursively was called with an argument that".
+ " isn't an RT::Principal or id. It's $principal");
+ return(undef);
+ }
+ return undef unless $id;
+
+ my $member_obj = RT::CachedGroupMember->new( $self->CurrentUser );
+ $member_obj->LoadByCols(
+ MemberId => $id,
+ GroupId => $self->PrincipalId
+ );
+
+ if ( my $member_id = $member_obj->id ) {
+ return $member_id;
+ }
+ else {
+ return (undef);
+ }
+}
+
+
+
+=head2 DeleteMember PRINCIPAL_ID
+
+Takes the principal id of a current user or group.
+If the current user has apropriate rights,
+removes that GroupMember from this group.
+Returns a two value array. the first value is true on successful
+addition or 0 on failure. The second value is a textual status msg.
+
+Optionally takes a hash of key value flags, such as RecordTransaction.
+
+=cut
+
+sub DeleteMember {
+ my $self = shift;
+ my $member_id = shift;
+
+
+ # We should only allow membership changes if the user has the right
+ # to modify group membership or the user is the principal in question
+ # and the user has the right to modify his own membership
+
+ unless ( (($member_id == $self->CurrentUser->PrincipalId) &&
+ $self->CurrentUserHasRight('ModifyOwnMembership') ) ||
+ $self->CurrentUserHasRight('AdminGroupMembership') ) {
+ #User has no permission to be doing this
+ return ( 0, $self->loc("Permission Denied") );
+ }
+ $self->_DeleteMember($member_id, @_);
+}
+
+# A helper subroutine for DeleteMember that bypasses the ACL checks
+# this should _ONLY_ ever be called from Ticket/Queue DeleteWatcher
+# when we want to deal with groups according to queue rights
+# In the dim future, this will all get factored out and life
+# will get better
+
+sub _DeleteMember {
+ my $self = shift;
+ my $member_id = shift;
+ my %args = (
+ RecordTransaction => 1,
+ @_,
+ );
+
+
+ my $member_obj = RT::GroupMember->new( $self->CurrentUser );
+
+ $member_obj->LoadByCols( MemberId => $member_id,
+ GroupId => $self->PrincipalId);
+
+
+ #If we couldn't load it, return undef.
+ unless ( $member_obj->Id() ) {
+ $RT::Logger->debug("Group has no member with that id");
+ return ( 0,$self->loc( "Group has no such member" ));
+ }
+
+ my $old_member = $member_obj->MemberId;
+
+ #Now that we've checked ACLs and sanity, delete the groupmember
+ my $val = $member_obj->Delete();
+
+ unless ($val) {
+ $RT::Logger->debug("Failed to delete group ".$self->Id." member ". $member_id);
+ return ( 0, $self->loc("Member not deleted" ));
+ }
+
+ if ($self->RoleClass) {
+ my %txn = (
+ OldValue => $old_member,
+ Field => $self->Name,
+ );
+
+ if ($self->SingleMemberRoleGroup) {
+ # _AddMember creates the Set-Owner txn (for example) but we handle
+ # the SetWatcher-Owner txn below.
+ $self->_AddMember(
+ PrincipalId => RT->Nobody->Id,
+ RecordTransaction => 0,
+ RecordSetTransaction => $args{RecordTransaction},
+ );
+ $txn{Type} = "SetWatcher";
+ $txn{NewValue} = RT->Nobody->id;
+ } else {
+ $txn{Type} = "DelWatcher";
+ }
+
+ if ($args{RecordTransaction}) {
+ my $obj = $args{Object} || $self->RoleGroupObject;
+ $obj->_NewTransaction(%txn);
+ }
+ }
+
+ # Record transactions for UserDefined groups
+ if ($args{RecordTransaction} && $self->Domain eq 'UserDefined') {
+ $member_obj->MemberObj->Object->_NewTransaction(
+ Type => 'DeleteMembership',
+ Field => $self->PrincipalObj->id,
+ );
+
+ $self->_NewTransaction(
+ Type => 'DeleteMember',
+ Field => $member_id,
+ );
+ }
+
+ return ( $val, $self->loc("Member deleted") );
+}
+
+
+
+sub _Set {
+ my $self = shift;
+ my %args = (
+ Field => undef,
+ Value => undef,
+ TransactionType => 'Set',
+ RecordTransaction => 1,
+ @_
+ );
+
+ unless ( $self->CurrentUserHasRight('AdminGroup') ) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+
+ my $Old = $self->SUPER::_Value("$args{'Field'}");
+
+ my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'},
+ Value => $args{'Value'} );
+
+ #If we can't actually set the field to the value, don't record
+ # a transaction. instead, get out of here.
+ if ( $ret == 0 ) { return ( 0, $msg ); }
+
+ if ( $args{'RecordTransaction'} == 1 ) {
+
+ my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
+ Type => $args{'TransactionType'},
+ Field => $args{'Field'},
+ NewValue => $args{'Value'},
+ OldValue => $Old,
+ TimeTaken => $args{'TimeTaken'},
+ );
+ return ( $Trans, scalar $TransObj->Description );
+ }
+ else {
+ return ( $ret, $msg );
+ }
+}
+
+=head2 CurrentUserCanSee
+
+Always returns 1; unfortunately, for historical reasons, users have
+always been able to examine groups they have indirect access to, even if
+they do not have SeeGroup explicitly.
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+ return 1;
+}
+
+
+=head2 PrincipalObj
+
+Returns the principal object for this user. returns an empty RT::Principal
+if there's no principal object matching this user.
+The response is cached. PrincipalObj should never ever change.
+
+
+=cut
+
+
+sub PrincipalObj {
+ my $self = shift;
+ my $res = RT::Principal->new( $self->CurrentUser );
+ $res->Load( $self->id );
+ return $res;
+}
+
+
+=head2 PrincipalId
+
+Returns this user's PrincipalId
+
+=cut
+
+sub PrincipalId {
+ my $self = shift;
+ return $self->Id;
+}
+
+sub InstanceObj {
+ my $self = shift;
+
+ my $class;
+ if ( $self->Domain eq 'ACLEquivalence' ) {
+ $class = "RT::User";
+ } elsif ($self->Domain eq 'RT::Queue-Role') {
+ $class = "RT::Queue";
+ } elsif ($self->Domain eq 'RT::Ticket-Role') {
+ $class = "RT::Ticket";
+ }
+
+ return unless $class;
+
+ my $obj = $class->new( $self->CurrentUser );
+ $obj->Load( $self->Instance );
+ return $obj;
+}
+
+sub BasicColumns {
+ (
+ [ Name => 'Name' ],
+ [ Description => 'Description' ],
+ );
+}
+
+
+=head1 AUTHOR
+
+Jesse Vincent, jesse@bestpractical.com
+
+=head1 SEE ALSO
+
+RT
+
+=cut
+
+
+
+
+
+=head2 id
+
+Returns the current value of id.
+(In the database, id is stored as int(11).)
+
+
+=cut
+
+
+=head2 Name
+
+Returns the current value of Name.