1 # {{{ BEGIN BPS TAGGED BLOCK
5 # This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 # CONTRIBUTION SUBMISSION POLICY:
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
45 # }}} END BPS TAGGED BLOCK
48 RT::Queue - an RT Queue object
68 no warnings qw(redefine);
70 use vars qw(@STATUS @ACTIVE_STATUS @INACTIVE_STATUS $RIGHTS);
76 @ACTIVE_STATUS = qw(new open stalled);
77 @INACTIVE_STATUS = qw(resolved rejected deleted);
78 @STATUS = (@ACTIVE_STATUS, @INACTIVE_STATUS);
80 # $self->loc('new'); # For the string extractor to get a string to localize
81 # $self->loc('open'); # For the string extractor to get a string to localize
82 # $self->loc('stalled'); # For the string extractor to get a string to localize
83 # $self->loc('resolved'); # For the string extractor to get a string to localize
84 # $self->loc('rejected'); # For the string extractor to get a string to localize
85 # $self->loc('deleted'); # For the string extractor to get a string to localize
89 SeeQueue => 'Can this principal see this queue', # loc_pair
90 AdminQueue => 'Create, delete and modify queues', # loc_pair
91 ShowACL => 'Display Access Control List', # loc_pair
92 ModifyACL => 'Modify Access Control List', # loc_pair
93 ModifyQueueWatchers => 'Modify the queue watchers', # loc_pair
94 AdminCustomFields => 'Create, delete and modify custom fields', # loc_pair
95 ModifyTemplate => 'Modify Scrip templates for this queue', # loc_pair
96 ShowTemplate => 'Display Scrip templates for this queue', # loc_pair
98 ModifyScrips => 'Modify Scrips for this queue', # loc_pair
99 ShowScrips => 'Display Scrips for this queue', # loc_pair
101 ShowTicket => 'See ticket summaries', # loc_pair
102 ShowTicketComments => 'See ticket private commentary', # loc_pair
103 ShowOutgoingEmail => 'See exact outgoing email messages and their recipeients', # loc_pair
105 Watch => 'Sign up as a ticket Requestor or ticket or queue Cc', # loc_pair
106 WatchAsAdminCc => 'Sign up as a ticket or queue AdminCc', # loc_pair
107 CreateTicket => 'Create tickets in this queue', # loc_pair
108 ReplyToTicket => 'Reply to tickets', # loc_pair
109 CommentOnTicket => 'Comment on tickets', # loc_pair
110 OwnTicket => 'Own tickets', # loc_pair
111 ModifyTicket => 'Modify tickets', # loc_pair
112 DeleteTicket => 'Delete tickets', # loc_pair
113 TakeTicket => 'Take tickets', # loc_pair
114 StealTicket => 'Steal tickets', # loc_pair
118 # Tell RT::ACE that this sort of object can get acls granted
119 $RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
121 # TODO: This should be refactored out into an RT::ACLedObject or something
122 # stuff the rights into a hash of rights that can exist.
124 foreach my $right ( keys %{$RIGHTS} ) {
125 $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
131 my %args = ( Target => '',
137 unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
138 return ( 0, $self->loc("Permission Denied") );
141 return $self->SUPER::_AddLink(%args);
154 unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
155 $RT::Logger->debug("No permission to delete links\n");
156 return ( 0, $self->loc('Permission Denied'))
159 return $self->SUPER::_DeleteLink(%args);
162 =head2 AvailableRights
164 Returns a hash of available rights for this object. The keys are the right names and the values are a description of what the rights do
168 sub AvailableRights {
173 # {{{ ActiveStatusArray
175 =head2 ActiveStatusArray
177 Returns an array of all ActiveStatuses for this queue
181 sub ActiveStatusArray {
183 return (@ACTIVE_STATUS);
188 # {{{ InactiveStatusArray
190 =head2 InactiveStatusArray
192 Returns an array of all InactiveStatuses for this queue
196 sub InactiveStatusArray {
198 return (@INACTIVE_STATUS);
207 Returns an array of all statuses for this queue
220 =head2 IsValidStatus VALUE
222 Returns true if VALUE is a valid status. Otherwise, returns 0
225 my $q = RT::Queue->new($RT::SystemUser);
226 ok($q->IsValidStatus('new')== 1, 'New is a valid status');
227 ok($q->IsValidStatus('f00')== 0, 'f00 is not a valid status');
235 my $retval = grep ( /^$value$/, $self->StatusArray );
244 =head2 IsActiveStatus VALUE
246 Returns true if VALUE is a Active status. Otherwise, returns 0
249 my $q = RT::Queue->new($RT::SystemUser);
250 ok($q->IsActiveStatus('new')== 1, 'New is a Active status');
251 ok($q->IsActiveStatus('rejected')== 0, 'Rejected is an inactive status');
252 ok($q->IsActiveStatus('f00')== 0, 'f00 is not a Active status');
260 my $retval = grep ( /^$value$/, $self->ActiveStatusArray );
267 # {{{ IsInactiveStatus
269 =head2 IsInactiveStatus VALUE
271 Returns true if VALUE is a Inactive status. Otherwise, returns 0
274 my $q = RT::Queue->new($RT::SystemUser);
275 ok($q->IsInactiveStatus('new')== 0, 'New is a Active status');
276 ok($q->IsInactiveStatus('rejected')== 1, 'rejeected is an Inactive status');
277 ok($q->IsInactiveStatus('f00')== 0, 'f00 is not a Active status');
281 sub IsInactiveStatus {
285 my $retval = grep ( /^$value$/, $self->InactiveStatusArray );
297 Create takes the name of the new queue
298 If you pass the ACL check, it creates the queue and returns its queue id.
306 CorrespondAddress => '',
308 CommentAddress => '',
309 InitialPriority => "0",
310 FinalPriority => "0",
315 unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
317 return ( 0, $self->loc("No permission to create queues") );
320 unless ( $self->ValidateName( $args{'Name'} ) ) {
321 return ( 0, $self->loc('Queue already exists') );
324 #TODO better input validation
325 $RT::Handle->BeginTransaction();
327 my $id = $self->SUPER::Create(%args);
329 $RT::Handle->Rollback();
330 return ( 0, $self->loc('Queue could not be created') );
333 my $create_ret = $self->_CreateQueueGroups();
334 unless ($create_ret) {
335 $RT::Handle->Rollback();
336 return ( 0, $self->loc('Queue could not be created') );
339 $RT::Handle->Commit();
340 return ( $id, $self->loc("Queue created") );
350 $self->loc('Deleting this object would break referential integrity') );
355 # {{{ sub SetDisabled
360 1 will cause this queue to no longer be avaialble for tickets.
361 0 will re-enable this queue
371 Takes either a numerical id or a textual Name and loads the specified queue.
378 my $identifier = shift;
379 if ( !$identifier ) {
383 if ( $identifier =~ /^(\d+)$/ ) {
384 $self->SUPER::LoadById($identifier);
387 $self->LoadByCols( Name => $identifier );
390 return ( $self->Id );
396 # {{{ sub ValidateName
398 =head2 ValidateName NAME
400 Takes a queue name. Returns true if it's an ok name for
401 a new queue. Returns undef if there's already a queue by that name.
409 my $tempqueue = new RT::Queue($RT::SystemUser);
410 $tempqueue->Load($name);
412 #If we couldn't load it :)
413 unless ( $tempqueue->id() ) {
417 #If this queue exists, return undef
418 #Avoid the ACL check.
419 if ( $tempqueue->Name() ) {
423 #If the queue doesn't exist, return 1
436 Returns an RT::Templates object of all of this queue's templates.
443 my $templates = RT::Templates->new( $self->CurrentUser );
445 if ( $self->CurrentUserHasRight('ShowTemplate') ) {
446 $templates->LimitToQueue( $self->id );
454 # {{{ Dealing with custom fields
458 =item CustomField NAME
460 Load the queue-specific custom field named NAME
467 my $cf = RT::CustomField->new($self->CurrentUser);
468 $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id);
477 Returns an RT::CustomFields object containing all global custom fields, as well as those tied to this queue
484 my $cfs = RT::CustomFields->new( $self->CurrentUser );
485 if ( $self->CurrentUserHasRight('SeeQueue') ) {
486 $cfs->LimitToGlobalOrQueue( $self->Id );
496 # {{{ Routines dealing with watchers.
498 # {{{ _CreateQueueGroups
500 =head2 _CreateQueueGroups
502 Create the ticket groups and links for this ticket.
503 This routine expects to be called from Ticket->Create _inside of a transaction_
505 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
507 It will return true on success and undef on failure.
511 my $Queue = RT::Queue->new($RT::SystemUser); my ($id, $msg) = $Queue->Create(Name => "Foo",
513 ok ($id, "Foo $id was created");
514 ok(my $group = RT::Group->new($RT::SystemUser));
515 ok($group->LoadQueueRoleGroup(Queue => $id, Type=> 'Cc'));
516 ok ($group->Id, "Found the requestors object for this Queue");
519 ok ((my $add_id, $add_msg) = $Queue->AddWatcher(Type => 'Cc', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor");
520 ok ($add_id, "Add succeeded: ($add_msg)");
521 ok(my $bob = RT::User->new($RT::SystemUser), "Creating a bob rt::user");
522 $bob->LoadByEmail('bob@fsck.com');
523 ok($bob->Id, "Found the bob rt user");
524 ok ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $bob->PrincipalId), "The Queue actually has bob at fsck.com as a requestor");;
525 ok ((my $add_id, $add_msg) = $Queue->DeleteWatcher(Type =>'Cc', Email => 'bob@fsck.com'), "Added bob at fsck.com as a requestor");
526 ok (!$Queue->IsWatcher(Type => 'Cc', Principal => $bob->PrincipalId), "The Queue no longer has bob at fsck.com as a requestor");;
529 $group = RT::Group->new($RT::SystemUser);
530 ok($group->LoadQueueRoleGroup(Queue => $id, Type=> 'Cc'));
531 ok ($group->Id, "Found the cc object for this Queue");
532 $group = RT::Group->new($RT::SystemUser);
533 ok($group->LoadQueueRoleGroup(Queue => $id, Type=> 'AdminCc'));
534 ok ($group->Id, "Found the AdminCc object for this Queue");
541 sub _CreateQueueGroups {
544 my @types = qw(Cc AdminCc Requestor Owner);
546 foreach my $type (@types) {
547 my $type_obj = RT::Group->new($self->CurrentUser);
548 my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id,
550 Domain => 'RT::Queue-Role');
552 $RT::Logger->error("Couldn't create a Queue group of type '$type' for ticket ".
553 $self->Id.": ".$msg);
568 AddWatcher takes a parameter hash. The keys are as follows:
570 Type One of Requestor, Cc, AdminCc
572 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
573 Email The email address of the new watcher. If a user with this
574 email address can't be found, a new nonprivileged user will be created.
576 If the watcher you\'re trying to set has an RT account, set the Owner paremeter to their User Id. Otherwise, set the Email parameter to their Email address.
578 Returns a tuple of (status/id, message).
586 PrincipalId => undef,
592 #If the watcher we're trying to add is for the current user
593 if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
594 # If it's an AdminCc and they don't have
595 # 'WatchAsAdminCc' or 'ModifyTicket', bail
596 if ( $args{'Type'} eq 'AdminCc' ) {
597 unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
598 or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
599 return ( 0, $self->loc('Permission Denied'))
603 # If it's a Requestor or Cc and they don't have
604 # 'Watch' or 'ModifyTicket', bail
605 elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
607 unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
608 or $self->CurrentUserHasRight('Watch') ) {
609 return ( 0, $self->loc('Permission Denied'))
613 $RT::Logger->warn( "$self -> AddWatcher got passed a bogus type");
614 return ( 0, $self->loc('Error in parameters to Queue->AddWatcher') );
618 # If the watcher isn't the current user
619 # and the current user doesn't have 'ModifyQueueWatcher'
622 unless ( $self->CurrentUserHasRight('ModifyQueueWatchers') ) {
623 return ( 0, $self->loc("Permission Denied") );
629 return ( $self->_AddWatcher(%args) );
632 #This contains the meat of AddWatcher. but can be called from a routine like
633 # Create, which doesn't need the additional acl check
639 PrincipalId => undef,
645 my $principal = RT::Principal->new($self->CurrentUser);
646 if ($args{'PrincipalId'}) {
647 $principal->Load($args{'PrincipalId'});
649 elsif ($args{'Email'}) {
651 my $user = RT::User->new($self->CurrentUser);
652 $user->LoadByEmail($args{'Email'});
655 $user->Load($args{'Email'});
657 if ($user->Id) { # If the user exists
658 $principal->Load($user->PrincipalId);
661 # if the user doesn't exist, we need to create a new user
662 my $new_user = RT::User->new($RT::SystemUser);
664 my ( $Address, $Name ) =
665 RT::EmailParser::ParseAddressFromHeader('', $args{'Email'});
667 my ( $Val, $Message ) = $new_user->Create(
669 EmailAddress => $Address,
672 Comments => 'Autocreated when added as a watcher');
674 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
675 # Deal with the race condition of two account creations at once
676 $new_user->LoadByEmail($args{'Email'});
678 $principal->Load($new_user->PrincipalId);
681 # If we can't find this watcher, we need to bail.
682 unless ($principal->Id) {
683 return(0, $self->loc("Could not find or create that user"));
687 my $group = RT::Group->new($self->CurrentUser);
688 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
689 unless ($group->id) {
690 return(0,$self->loc("Group not found"));
693 if ( $group->HasMember( $principal)) {
695 return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
699 my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
701 $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id."\n".$m_msg);
703 return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
705 return ( 1, $self->loc('Added principal as a [_1] for this queue', $args{'Type'}) );
710 # {{{ sub DeleteWatcher
712 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
715 Deletes a queue watcher. Takes two arguments:
717 Type (one of Requestor,Cc,AdminCc)
721 PrincipalId (an RT::Principal Id of the watcher you want to remove)
723 Email (the email address of an existing wathcer)
732 my %args = ( Type => undef,
733 PrincipalId => undef,
736 unless ($args{'PrincipalId'} ) {
737 return(0, $self->loc("No principal specified"));
739 my $principal = RT::Principal->new($self->CurrentUser);
740 $principal->Load($args{'PrincipalId'});
742 # If we can't find this watcher, we need to bail.
743 unless ($principal->Id) {
744 return(0, $self->loc("Could not find that principal"));
747 my $group = RT::Group->new($self->CurrentUser);
748 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
749 unless ($group->id) {
750 return(0,$self->loc("Group not found"));
754 #If the watcher we're trying to add is for the current user
755 if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
756 # If it's an AdminCc and they don't have
757 # 'WatchAsAdminCc' or 'ModifyQueue', bail
758 if ( $args{'Type'} eq 'AdminCc' ) {
759 unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
760 or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
761 return ( 0, $self->loc('Permission Denied'))
765 # If it's a Requestor or Cc and they don't have
766 # 'Watch' or 'ModifyQueue', bail
767 elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
768 unless ( $self->CurrentUserHasRight('ModifyQueueWatchers')
769 or $self->CurrentUserHasRight('Watch') ) {
770 return ( 0, $self->loc('Permission Denied'))
774 $RT::Logger->warn( "$self -> DeleteWatcher got passed a bogus type");
775 return ( 0, $self->loc('Error in parameters to Queue->DeleteWatcher') );
779 # If the watcher isn't the current user
780 # and the current user doesn't have 'ModifyQueueWathcers' bail
782 unless ( $self->CurrentUserHasRight('ModifyQueueWatchers') ) {
783 return ( 0, $self->loc("Permission Denied") );
790 # see if this user is already a watcher.
792 unless ( $group->HasMember($principal)) {
794 $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
797 my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
799 $RT::Logger->error("Failed to delete ".$principal->Id.
800 " as a member of group ".$group->Id."\n".$m_msg);
802 return ( 0, $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
805 return ( 1, $self->loc("[_1] is no longer a [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
810 # {{{ AdminCcAddresses
812 =head2 AdminCcAddresses
814 returns String: All queue AdminCc email addresses as a string
818 sub AdminCcAddresses {
821 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
825 return ( $self->AdminCc->MemberEmailAddressesAsString )
835 returns String: All queue Ccs as a string of email addresses
842 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
846 return ( $self->Cc->MemberEmailAddressesAsString);
857 Returns an RT::Group object which contains this Queue's Ccs.
858 If the user doesn't have "ShowQueue" permission, returns an empty group
865 my $group = RT::Group->new($self->CurrentUser);
866 if ( $self->CurrentUserHasRight('SeeQueue') ) {
867 $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
880 Returns an RT::Group object which contains this Queue's AdminCcs.
881 If the user doesn't have "ShowQueue" permission, returns an empty group
888 my $group = RT::Group->new($self->CurrentUser);
889 if ( $self->CurrentUserHasRight('SeeQueue') ) {
890 $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
898 # {{{ IsWatcher, IsCc, IsAdminCc
901 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
903 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
905 Takes a param hash with the attributes Type and PrincipalId
907 Type is one of Requestor, Cc, AdminCc and Owner
909 PrincipalId is an RT::Principal id
911 Returns true if that principal is a member of the group Type for this queue
919 my %args = ( Type => 'Cc',
920 PrincipalId => undef,
924 # Load the relevant group.
925 my $group = RT::Group->new($self->CurrentUser);
926 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
927 # Ask if it has the member in question
929 my $principal = RT::Principal->new($self->CurrentUser);
930 $principal->Load($args{'PrincipalId'});
931 unless ($principal->Id) {
935 return ($group->HasMemberRecursively($principal));
943 =head2 IsCc PRINCIPAL_ID
945 Takes an RT::Principal id.
946 Returns true if the principal is a requestor of the current queue.
955 return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
963 =head2 IsAdminCc PRINCIPAL_ID
965 Takes an RT::Principal id.
966 Returns true if the principal is a requestor of the current queue.
974 return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
995 unless ( $self->CurrentUserHasRight('AdminQueue') ) {
996 return ( 0, $self->loc('Permission Denied') );
998 return ( $self->SUPER::_Set(@_) );
1008 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1012 return ( $self->__Value(@_) );
1017 # {{{ sub CurrentUserHasRight
1019 =head2 CurrentUserHasRight
1021 Takes one argument. A textual string with the name of the right we want to check.
1022 Returns true if the current user has that right for this queue.
1023 Returns undef otherwise.
1027 sub CurrentUserHasRight {
1033 Principal => $self->CurrentUser,
1046 Takes a param hash with the fields 'Right' and 'Principal'.
1047 Principal defaults to the current user.
1048 Returns true if the principal has that right for this queue.
1049 Returns undef otherwise.
1053 # TAKES: Right and optional "Principal" which defaults to the current user
1058 Principal => $self->CurrentUser,
1061 unless ( defined $args{'Principal'} ) {
1062 $RT::Logger->debug("Principal undefined in Queue::HasRight");
1066 $args{'Principal'}->HasRight(
1068 Right => $args{'Right'}