limit ticket creation queue dropdowns based on ACL, RT#7778
[freeside.git] / rt / lib / RT / Queue_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 #                                          <jesse@bestpractical.com>
7
8 # (Except where explicitly superseded by other copyright notices)
9
10
11 # LICENSE:
12
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
16 # from www.gnu.org.
17
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.
22
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., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28
29
30 # CONTRIBUTION SUBMISSION POLICY:
31
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46
47 # END BPS TAGGED BLOCK }}}
48
49 =head1 NAME
50
51   RT::Queue - an RT Queue object
52
53 =head1 SYNOPSIS
54
55   use RT::Queue;
56
57 =head1 DESCRIPTION
58
59 An RT queue object.
60
61 =head1 METHODS
62
63 =cut
64
65
66 package RT::Queue;
67
68 use strict;
69 no warnings qw(redefine);
70
71 use RT::Groups;
72 use RT::ACL;
73 use RT::Interface::Email;
74
75 our @DEFAULT_ACTIVE_STATUS = qw(new open stalled);
76 our @DEFAULT_INACTIVE_STATUS = qw(resolved rejected deleted);  
77
78 # $self->loc('new'); # For the string extractor to get a string to localize
79 # $self->loc('open'); # For the string extractor to get a string to localize
80 # $self->loc('stalled'); # For the string extractor to get a string to localize
81 # $self->loc('resolved'); # For the string extractor to get a string to localize
82 # $self->loc('rejected'); # For the string extractor to get a string to localize
83 # $self->loc('deleted'); # For the string extractor to get a string to localize
84
85
86 our $RIGHTS = {
87     SeeQueue            => 'Can this principal see this queue',       # loc_pair
88     AdminQueue          => 'Create, delete and modify queues',        # loc_pair
89     ShowACL             => 'Display Access Control List',             # loc_pair
90     ModifyACL           => 'Modify Access Control List',              # loc_pair
91     ModifyQueueWatchers => 'Modify the queue watchers',               # loc_pair
92     SeeCustomField     => 'See custom field values',                 # loc_pair
93     ModifyCustomField  => 'Modify custom field values',              # loc_pair
94     AssignCustomFields  => 'Assign and remove custom fields',         # loc_pair
95     ModifyTemplate      => 'Modify Scrip templates for this queue',   # loc_pair
96     ShowTemplate        => 'Display Scrip templates for this queue',  # loc_pair
97
98     ModifyScrips => 'Modify Scrips for this queue',                   # loc_pair
99     ShowScrips   => 'Display Scrips for this queue',                  # loc_pair
100
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
104
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
115
116     ForwardMessage  => 'Forward messages to third person(s)',         # loc_pair
117
118 };
119
120 # Tell RT::ACE that this sort of object can get acls granted
121 $RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
122
123 # TODO: This should be refactored out into an RT::ACLedObject or something
124 # stuff the rights into a hash of rights that can exist.
125
126 foreach my $right ( keys %{$RIGHTS} ) {
127     $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
128 }
129
130 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
131
132 Adds the given rights to the list of possible rights.  This method
133 should be called during server startup, not at runtime.
134
135 =cut
136
137 sub AddRights {
138     my $self = shift;
139     my %new = @_;
140     $RIGHTS = { %$RIGHTS, %new };
141     %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
142                                       map { lc($_) => $_ } keys %new);
143 }
144
145 sub AddLink {
146     my $self = shift;
147     my %args = ( Target => '',
148                  Base   => '',
149                  Type   => '',
150                  Silent => undef,
151                  @_ );
152
153     unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
154         return ( 0, $self->loc("Permission Denied") );
155     }
156
157     return $self->SUPER::_AddLink(%args);
158 }
159
160 sub DeleteLink {
161     my $self = shift;
162     my %args = (
163         Base   => undef,
164         Target => undef,
165         Type   => undef,
166         @_
167     );
168
169     #check acls
170     unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
171         $RT::Logger->debug("No permission to delete links");
172         return ( 0, $self->loc('Permission Denied'))
173     }
174
175     return $self->SUPER::_DeleteLink(%args);
176 }
177
178 =head2 AvailableRights
179
180 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
181
182 =cut
183
184 sub AvailableRights {
185     my $self = shift;
186     return($RIGHTS);
187 }
188
189 # {{{ ActiveStatusArray
190
191 =head2 ActiveStatusArray
192
193 Returns an array of all ActiveStatuses for this queue
194
195 =cut
196
197 sub ActiveStatusArray {
198     my $self = shift;
199     if (RT->Config->Get('ActiveStatus')) {
200         return (RT->Config->Get('ActiveStatus'))
201     } else {
202         $RT::Logger->warning("RT::ActiveStatus undefined, falling back to deprecated defaults");
203         return (@DEFAULT_ACTIVE_STATUS);
204     }
205 }
206
207 # }}}
208
209 # {{{ InactiveStatusArray
210
211 =head2 InactiveStatusArray
212
213 Returns an array of all InactiveStatuses for this queue
214
215 =cut
216
217 sub InactiveStatusArray {
218     my $self = shift;
219     if (RT->Config->Get('InactiveStatus')) {
220         return (RT->Config->Get('InactiveStatus'))
221     } else {
222         $RT::Logger->warning("RT::InactiveStatus undefined, falling back to deprecated defaults");
223         return (@DEFAULT_INACTIVE_STATUS);
224     }
225 }
226
227 # }}}
228
229 # {{{ StatusArray
230
231 =head2 StatusArray
232
233 Returns an array of all statuses for this queue
234
235 =cut
236
237 sub StatusArray {
238     my $self = shift;
239     return ($self->ActiveStatusArray(), $self->InactiveStatusArray());
240 }
241
242 # }}}
243
244 # {{{ IsValidStatus
245
246 =head2 IsValidStatus VALUE
247
248 Returns true if VALUE is a valid status.  Otherwise, returns 0.
249
250
251 =cut
252
253 sub IsValidStatus {
254     my $self  = shift;
255     my $value = shift;
256
257     my $retval = grep ( $_ eq $value, $self->StatusArray );
258     return ($retval);
259
260 }
261
262 # }}}
263
264 # {{{ IsActiveStatus
265
266 =head2 IsActiveStatus VALUE
267
268 Returns true if VALUE is a Active status.  Otherwise, returns 0
269
270
271 =cut
272
273 sub IsActiveStatus {
274     my $self  = shift;
275     my $value = shift;
276
277     my $retval = grep ( $_ eq $value, $self->ActiveStatusArray );
278     return ($retval);
279
280 }
281
282 # }}}
283
284 # {{{ IsInactiveStatus
285
286 =head2 IsInactiveStatus VALUE
287
288 Returns true if VALUE is a Inactive status.  Otherwise, returns 0
289
290
291 =cut
292
293 sub IsInactiveStatus {
294     my $self  = shift;
295     my $value = shift;
296
297     my $retval = grep ( $_ eq $value, $self->InactiveStatusArray );
298     return ($retval);
299
300 }
301
302 # }}}
303
304
305 # {{{ sub Create
306
307
308
309
310 =head2 Create(ARGS)
311
312 Arguments: ARGS is a hash of named parameters.  Valid parameters are:
313
314   Name (required)
315   Description
316   CorrespondAddress
317   CommentAddress
318   InitialPriority
319   FinalPriority
320   DefaultDueIn
321  
322 If you pass the ACL check, it creates the queue and returns its queue id.
323
324
325 =cut
326
327 sub Create {
328     my $self = shift;
329     my %args = (
330         Name              => undef,
331         CorrespondAddress => '',
332         Description       => '',
333         CommentAddress    => '',
334         SubjectTag        => '',
335         InitialPriority   => 0,
336         FinalPriority     => 0,
337         DefaultDueIn      => 0,
338         Sign              => undef,
339         Encrypt           => undef,
340         _RecordTransaction => 1,
341         @_
342     );
343
344     unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
345     {    #Check them ACLs
346         return ( 0, $self->loc("No permission to create queues") );
347     }
348
349     unless ( $self->ValidateName( $args{'Name'} ) ) {
350         return ( 0, $self->loc('Queue already exists') );
351     }
352
353     my %attrs = map {$_ => 1} $self->ReadableAttributes;
354
355     #TODO better input validation
356     $RT::Handle->BeginTransaction();
357     my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs );
358     unless ($id) {
359         $RT::Handle->Rollback();
360         return ( 0, $self->loc('Queue could not be created') );
361     }
362
363     my $create_ret = $self->_CreateQueueGroups();
364     unless ($create_ret) {
365         $RT::Handle->Rollback();
366         return ( 0, $self->loc('Queue could not be created') );
367     }
368     if ( $args{'_RecordTransaction'} ) {
369         $self->_NewTransaction( Type => "Create" );
370     }
371     $RT::Handle->Commit;
372
373     if ( defined $args{'Sign'} ) {
374         my ($status, $msg) = $self->SetSign( $args{'Sign'} );
375         $RT::Logger->error("Couldn't set attribute 'Sign': $msg")
376             unless $status;
377     }
378     if ( defined $args{'Encrypt'} ) {
379         my ($status, $msg) = $self->SetEncrypt( $args{'Encrypt'} );
380         $RT::Logger->error("Couldn't set attribute 'Encrypt': $msg")
381             unless $status;
382     }
383
384     RT->System->QueueCacheNeedsUpdate(1);
385
386     return ( $id, $self->loc("Queue created") );
387 }
388
389 # }}}
390
391 # {{{ sub Delete 
392
393 sub Delete {
394     my $self = shift;
395     return ( 0,
396         $self->loc('Deleting this object would break referential integrity') );
397 }
398
399 # }}}
400
401 # {{{ sub SetDisabled
402
403 =head2 SetDisabled
404
405 Takes a boolean.
406 1 will cause this queue to no longer be available for tickets.
407 0 will re-enable this queue.
408
409 =cut
410
411 sub SetDisabled {
412     my $self = shift;
413     my $val = shift;
414
415     $RT::Handle->BeginTransaction();
416     my $set_err = $self->SUPER::SetDisabled($val);
417     unless ($set_err) {
418         $RT::Handle->Rollback();
419         $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." queue ".$self->PrincipalObj->Id);
420         return (undef);
421     }
422     $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
423
424     $RT::Handle->Commit();
425
426     RT->System->QueueCacheNeedsUpdate(1);
427
428     if ( $val == 1 ) {
429         return (1, $self->loc("Queue disabled"));
430     } else {
431         return (1, $self->loc("Queue enabled"));
432     }
433
434 }
435
436 # }}}
437
438 # {{{ sub Load 
439
440 =head2 Load
441
442 Takes either a numerical id or a textual Name and loads the specified queue.
443
444 =cut
445
446 sub Load {
447     my $self = shift;
448
449     my $identifier = shift;
450     if ( !$identifier ) {
451         return (undef);
452     }
453
454     if ( $identifier =~ /^(\d+)$/ ) {
455         $self->SUPER::LoadById($identifier);
456     }
457     else {
458         $self->LoadByCols( Name => $identifier );
459     }
460
461     return ( $self->Id );
462
463 }
464
465 # }}}
466
467 # {{{ sub ValidateName
468
469 =head2 ValidateName NAME
470
471 Takes a queue name. Returns true if it's an ok name for
472 a new queue. Returns undef if there's already a queue by that name.
473
474 =cut
475
476 sub ValidateName {
477     my $self = shift;
478     my $name = shift;
479
480     my $tempqueue = new RT::Queue($RT::SystemUser);
481     $tempqueue->Load($name);
482
483     #If this queue exists, return undef
484     if ( $tempqueue->Name() && $tempqueue->id != $self->id)  {
485         return (undef);
486     }
487
488     #If the queue doesn't exist, return 1
489     else {
490         return ($self->SUPER::ValidateName($name));
491     }
492
493 }
494
495 # }}}
496
497 =head2 SetSign
498
499 =cut
500
501 sub Sign {
502     my $self = shift;
503     my $value = shift;
504
505     return undef unless $self->CurrentUserHasRight('SeeQueue');
506     my $attr = $self->FirstAttribute('Sign') or return 0;
507     return $attr->Content;
508 }
509
510 sub SetSign {
511     my $self = shift;
512     my $value = shift;
513
514     return ( 0, $self->loc('Permission Denied') )
515         unless $self->CurrentUserHasRight('AdminQueue');
516
517     my ($status, $msg) = $self->SetAttribute(
518         Name        => 'Sign',
519         Description => 'Sign outgoing messages by default',
520         Content     => $value,
521     );
522     return ($status, $msg) unless $status;
523     return ($status, $self->loc('Signing enabled')) if $value;
524     return ($status, $self->loc('Signing disabled'));
525 }
526
527 sub Encrypt {
528     my $self = shift;
529     my $value = shift;
530
531     return undef unless $self->CurrentUserHasRight('SeeQueue');
532     my $attr = $self->FirstAttribute('Encrypt') or return 0;
533     return $attr->Content;
534 }
535
536 sub SetEncrypt {
537     my $self = shift;
538     my $value = shift;
539
540     return ( 0, $self->loc('Permission Denied') )
541         unless $self->CurrentUserHasRight('AdminQueue');
542
543     my ($status, $msg) = $self->SetAttribute(
544         Name        => 'Encrypt',
545         Description => 'Encrypt outgoing messages by default',
546         Content     => $value,
547     );
548     return ($status, $msg) unless $status;
549     return ($status, $self->loc('Encrypting enabled')) if $value;
550     return ($status, $self->loc('Encrypting disabled'));
551 }
552
553 sub SubjectTag {
554     my $self = shift;
555     return RT->System->SubjectTag( $self );
556 }
557
558 sub SetSubjectTag {
559     my $self = shift;
560     my $value = shift;
561
562     return ( 0, $self->loc('Permission Denied') )
563         unless $self->CurrentUserHasRight('AdminQueue');
564
565     my $attr = RT->System->FirstAttribute('BrandedSubjectTag');
566     my $map = $attr ? $attr->Content : {};
567     if ( defined $value && length $value ) {
568         $map->{ $self->id } = $value;
569     } else {
570         delete $map->{ $self->id };
571     }
572
573     my ($status, $msg) = RT->System->SetAttribute(
574         Name        => 'BrandedSubjectTag',
575         Description => 'Queue id => subject tag map',
576         Content     => $map,
577     );
578     return ($status, $msg) unless $status;
579     return ($status, $self->loc(
580         "SubjectTag changed to [_1]", 
581         (defined $value && length $value)? $value : $self->loc("(no value)")
582     ))
583 }
584
585 # {{{ sub Templates
586
587 =head2 Templates
588
589 Returns an RT::Templates object of all of this queue's templates.
590
591 =cut
592
593 sub Templates {
594     my $self = shift;
595
596     my $templates = RT::Templates->new( $self->CurrentUser );
597
598     if ( $self->CurrentUserHasRight('ShowTemplate') ) {
599         $templates->LimitToQueue( $self->id );
600     }
601
602     return ($templates);
603 }
604
605 # }}}
606
607 # {{{ Dealing with custom fields
608
609 # {{{  CustomField
610
611 =head2 CustomField NAME
612
613 Load the queue-specific custom field named NAME
614
615 =cut
616
617 sub CustomField {
618     my $self = shift;
619     my $name = shift;
620     my $cf = RT::CustomField->new($self->CurrentUser);
621     $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id); 
622     return ($cf);
623 }
624
625
626 # {{{ TicketCustomFields
627
628 =head2 TicketCustomFields
629
630 Returns an L<RT::CustomFields> object containing all global and
631 queue-specific B<ticket> custom fields.
632
633 =cut
634
635 sub TicketCustomFields {
636     my $self = shift;
637
638     my $cfs = RT::CustomFields->new( $self->CurrentUser );
639     if ( $self->CurrentUserHasRight('SeeQueue') ) {
640         $cfs->SetContextObject( $self );
641         $cfs->LimitToGlobalOrObjectId( $self->Id );
642         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' );
643         $cfs->ApplySortOrder;
644     }
645     return ($cfs);
646 }
647
648 # }}}
649
650 # {{{ TicketTransactionCustomFields
651
652 =head2 TicketTransactionCustomFields
653
654 Returns an L<RT::CustomFields> object containing all global and
655 queue-specific B<transaction> custom fields.
656
657 =cut
658
659 sub TicketTransactionCustomFields {
660     my $self = shift;
661
662     my $cfs = RT::CustomFields->new( $self->CurrentUser );
663     if ( $self->CurrentUserHasRight('SeeQueue') ) {
664         $cfs->LimitToGlobalOrObjectId( $self->Id );
665         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
666         $cfs->ApplySortOrder;
667     }
668     return ($cfs);
669 }
670
671 # }}}
672
673 # }}}
674
675
676 # {{{ Routines dealing with watchers.
677
678 # {{{ _CreateQueueGroups 
679
680 =head2 _CreateQueueGroups
681
682 Create the ticket groups and links for this ticket. 
683 This routine expects to be called from Ticket->Create _inside of a transaction_
684
685 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
686
687 It will return true on success and undef on failure.
688
689
690 =cut
691
692
693 sub _CreateQueueGroups {
694     my $self = shift;
695
696     my @types = qw(Cc AdminCc Requestor Owner);
697
698     foreach my $type (@types) {
699         my $type_obj = RT::Group->new($self->CurrentUser);
700         my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id, 
701                                                      Type => $type,
702                                                      Domain => 'RT::Queue-Role');
703         unless ($id) {
704             $RT::Logger->error("Couldn't create a Queue group of type '$type' for ticket ".
705                                $self->Id.": ".$msg);
706             return(undef);
707         }
708      }
709     return(1);
710    
711 }
712
713
714 # }}}
715
716 # {{{ sub AddWatcher
717
718 =head2 AddWatcher
719
720 AddWatcher takes a parameter hash. The keys are as follows:
721
722 Type        One of Requestor, Cc, AdminCc
723
724 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
725 Email       The email address of the new watcher. If a user with this 
726             email address can't be found, a new nonprivileged user will be created.
727
728 If the watcher you\'re trying to set has an RT account, set the Owner parameter to their User Id. Otherwise, set the Email parameter to their Email address.
729
730 Returns a tuple of (status/id, message).
731
732 =cut
733
734 sub AddWatcher {
735     my $self = shift;
736     my %args = (
737         Type  => undef,
738         PrincipalId => undef,
739         Email => undef,
740         @_
741     );
742
743     return ( 0, "No principal specified" )
744         unless $args{'Email'} or $args{'PrincipalId'};
745
746     if ( !$args{'PrincipalId'} && $args{'Email'} ) {
747         my $user = RT::User->new( $self->CurrentUser );
748         $user->LoadByEmail( $args{'Email'} );
749         $args{'PrincipalId'} = $user->PrincipalId if $user->id;
750     }
751
752     # {{{ Check ACLS
753     return ( $self->_AddWatcher(%args) )
754         if $self->CurrentUserHasRight('ModifyQueueWatchers');
755
756     #If the watcher we're trying to add is for the current user
757     if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
758         #  If it's an AdminCc and they don't have 
759         #   'WatchAsAdminCc' or 'ModifyTicket', bail
760         if ( defined $args{'Type'} && ($args{'Type'} eq 'AdminCc') ) {
761             return ( $self->_AddWatcher(%args) )
762                 if $self->CurrentUserHasRight('WatchAsAdminCc');
763         }
764
765         #  If it's a Requestor or Cc and they don't have
766         #   'Watch' or 'ModifyTicket', bail
767         elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
768             return ( $self->_AddWatcher(%args) )
769                 if $self->CurrentUserHasRight('Watch');
770         }
771         else {
772             $RT::Logger->warning( "$self -> AddWatcher got passed a bogus type");
773             return ( 0, $self->loc('Error in parameters to Queue->AddWatcher') );
774         }
775     }
776
777     return ( 0, $self->loc("Permission Denied") );
778 }
779
780 #This contains the meat of AddWatcher. but can be called from a routine like
781 # Create, which doesn't need the additional acl check
782 sub _AddWatcher {
783     my $self = shift;
784     my %args = (
785         Type   => undef,
786         Silent => undef,
787         PrincipalId => undef,
788         Email => undef,
789         @_
790     );
791
792
793     my $principal = RT::Principal->new( $self->CurrentUser );
794     if ( $args{'PrincipalId'} ) {
795         $principal->Load( $args{'PrincipalId'} );
796         if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
797             return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email, $self->loc($args{'Type'})))
798                 if RT::EmailParser->IsRTAddress( $email );
799         }
800     }
801     elsif ( $args{'Email'} ) {
802         if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
803             return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $args{'Email'}, $self->loc($args{'Type'})));
804         }
805         my $user = RT::User->new($self->CurrentUser);
806         $user->LoadByEmail( $args{'Email'} );
807         $user->Load( $args{'Email'} )
808             unless $user->id;
809
810         if ( $user->Id ) { # If the user exists
811             $principal->Load( $user->PrincipalId );
812         } else {
813             # if the user doesn't exist, we need to create a new user
814             my $new_user = RT::User->new($RT::SystemUser);
815
816             my ( $Address, $Name ) =  
817                RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
818
819             my ( $Val, $Message ) = $new_user->Create(
820                 Name         => $Address,
821                 EmailAddress => $Address,
822                 RealName     => $Name,
823                 Privileged   => 0,
824                 Comments     => 'Autocreated when added as a watcher'
825             );
826             unless ($Val) {
827                 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
828                 # Deal with the race condition of two account creations at once
829                 $new_user->LoadByEmail( $args{'Email'} );
830             }
831             $principal->Load( $new_user->PrincipalId );
832         }
833     }
834     # If we can't find this watcher, we need to bail.
835     unless ( $principal->Id ) {
836         return(0, $self->loc("Could not find or create that user"));
837     }
838
839     my $group = RT::Group->new($self->CurrentUser);
840     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
841     unless ($group->id) {
842         return(0,$self->loc("Group not found"));
843     }
844
845     if ( $group->HasMember( $principal)) {
846
847         return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
848     }
849
850
851     my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
852     unless ($m_id) {
853         $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
854
855         return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
856     }
857     return ( 1, $self->loc('Added principal as a [_1] for this queue', $args{'Type'}) );
858 }
859
860 # }}}
861
862 # {{{ sub DeleteWatcher
863
864 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
865
866
867 Deletes a queue  watcher.  Takes two arguments:
868
869 Type  (one of Requestor,Cc,AdminCc)
870
871 and one of
872
873 PrincipalId (an RT::Principal Id of the watcher you want to remove)
874     OR
875 Email (the email address of an existing wathcer)
876
877
878 =cut
879
880
881 sub DeleteWatcher {
882     my $self = shift;
883
884     my %args = ( Type => undef,
885                  PrincipalId => undef,
886                  Email => undef,
887                  @_ );
888
889     unless ( $args{'PrincipalId'} || $args{'Email'} ) {
890         return ( 0, $self->loc("No principal specified") );
891     }
892
893     if ( !$args{PrincipalId} and $args{Email} ) {
894         my $user = RT::User->new( $self->CurrentUser );
895         my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
896         $args{PrincipalId} = $user->PrincipalId if $rv;
897     }
898     
899     my $principal = RT::Principal->new( $self->CurrentUser );
900     if ( $args{'PrincipalId'} ) {
901         $principal->Load( $args{'PrincipalId'} );
902     }
903     else {
904         my $user = RT::User->new( $self->CurrentUser );
905         $user->LoadByEmail( $args{'Email'} );
906         $principal->Load( $user->Id );
907     }
908
909     # If we can't find this watcher, we need to bail.
910     unless ( $principal->Id ) {
911         return ( 0, $self->loc("Could not find that principal") );
912     }
913
914     my $group = RT::Group->new($self->CurrentUser);
915     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
916     unless ($group->id) {
917         return(0,$self->loc("Group not found"));
918     }
919
920     my $can_modify_queue = $self->CurrentUserHasRight('ModifyQueueWatchers');
921
922     # {{{ Check ACLS
923     #If the watcher we're trying to add is for the current user
924     if ( defined $args{'PrincipalId'} and $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
925         #  If it's an AdminCc and they don't have 
926         #   'WatchAsAdminCc' or 'ModifyQueue', bail
927         if ( $args{'Type'} eq 'AdminCc' ) {
928             unless ( $can_modify_queue
929                 or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
930                 return ( 0, $self->loc('Permission Denied'))
931             }
932         }
933
934         #  If it's a Requestor or Cc and they don't have
935         #   'Watch' or 'ModifyQueue', bail
936         elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
937             unless ( $can_modify_queue
938                 or $self->CurrentUserHasRight('Watch') ) {
939                 return ( 0, $self->loc('Permission Denied'))
940             }
941         }
942         else {
943             $RT::Logger->warning( "$self -> DeleteWatcher got passed a bogus type");
944             return ( 0, $self->loc('Error in parameters to Queue->DeleteWatcher') );
945         }
946     }
947
948     # If the watcher isn't the current user 
949     # and the current user  doesn't have 'ModifyQueueWathcers' bail
950     else {
951         unless ( $can_modify_queue ) {
952             return ( 0, $self->loc("Permission Denied") );
953         }
954     }
955
956     # }}}
957
958
959     # see if this user is already a watcher.
960
961     unless ( $group->HasMember($principal)) {
962         return ( 0,
963         $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
964     }
965
966     my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
967     unless ($m_id) {
968         $RT::Logger->error("Failed to delete ".$principal->Id.
969                            " as a member of group ".$group->Id.": ".$m_msg);
970
971         return ( 0,    $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
972     }
973
974     return ( 1, $self->loc("[_1] is no longer a [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
975 }
976
977 # }}}
978
979 # {{{ AdminCcAddresses
980
981 =head2 AdminCcAddresses
982
983 returns String: All queue AdminCc email addresses as a string
984
985 =cut
986
987 sub AdminCcAddresses {
988     my $self = shift;
989     
990     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
991         return undef;
992     }   
993     
994     return ( $self->AdminCc->MemberEmailAddressesAsString )
995     
996 }   
997
998 # }}}
999
1000 # {{{ CcAddresses
1001
1002 =head2 CcAddresses
1003
1004 returns String: All queue Ccs as a string of email addresses
1005
1006 =cut
1007
1008 sub CcAddresses {
1009     my $self = shift;
1010
1011     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1012         return undef;
1013     }
1014
1015     return ( $self->Cc->MemberEmailAddressesAsString);
1016
1017 }
1018 # }}}
1019
1020
1021 # {{{ sub Cc
1022
1023 =head2 Cc
1024
1025 Takes nothing.
1026 Returns an RT::Group object which contains this Queue's Ccs.
1027 If the user doesn't have "ShowQueue" permission, returns an empty group
1028
1029 =cut
1030
1031 sub Cc {
1032     my $self = shift;
1033
1034     my $group = RT::Group->new($self->CurrentUser);
1035     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1036         $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1037     }
1038     return ($group);
1039
1040 }
1041
1042 # }}}
1043
1044 # {{{ sub AdminCc
1045
1046 =head2 AdminCc
1047
1048 Takes nothing.
1049 Returns an RT::Group object which contains this Queue's AdminCcs.
1050 If the user doesn't have "ShowQueue" permission, returns an empty group
1051
1052 =cut
1053
1054 sub AdminCc {
1055     my $self = shift;
1056
1057     my $group = RT::Group->new($self->CurrentUser);
1058     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1059         $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1060     }
1061     return ($group);
1062
1063 }
1064
1065 # }}}
1066
1067 # {{{ IsWatcher, IsCc, IsAdminCc
1068
1069 # {{{ sub IsWatcher
1070 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1071
1072 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1073
1074 Takes a param hash with the attributes Type and PrincipalId
1075
1076 Type is one of Requestor, Cc, AdminCc and Owner
1077
1078 PrincipalId is an RT::Principal id 
1079
1080 Returns true if that principal is a member of the group Type for this queue
1081
1082
1083 =cut
1084
1085 sub IsWatcher {
1086     my $self = shift;
1087
1088     my %args = ( Type  => 'Cc',
1089         PrincipalId    => undef,
1090         @_
1091     );
1092
1093     # Load the relevant group. 
1094     my $group = RT::Group->new($self->CurrentUser);
1095     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1096     # Ask if it has the member in question
1097
1098     my $principal = RT::Principal->new($self->CurrentUser);
1099     $principal->Load($args{'PrincipalId'});
1100     unless ($principal->Id) {
1101         return (undef);
1102     }
1103
1104     return ($group->HasMemberRecursively($principal));
1105 }
1106
1107 # }}}
1108
1109
1110 # {{{ sub IsCc
1111
1112 =head2 IsCc PRINCIPAL_ID
1113
1114 Takes an RT::Principal id.
1115 Returns true if the principal is a requestor of the current queue.
1116
1117
1118 =cut
1119
1120 sub IsCc {
1121     my $self = shift;
1122     my $cc   = shift;
1123
1124     return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1125
1126 }
1127
1128 # }}}
1129
1130 # {{{ sub IsAdminCc
1131
1132 =head2 IsAdminCc PRINCIPAL_ID
1133
1134 Takes an RT::Principal id.
1135 Returns true if the principal is a requestor of the current queue.
1136
1137 =cut
1138
1139 sub IsAdminCc {
1140     my $self   = shift;
1141     my $person = shift;
1142
1143     return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1144
1145 }
1146
1147 # }}}
1148
1149
1150 # }}}
1151
1152
1153
1154
1155
1156 # }}}
1157
1158 # {{{ ACCESS CONTROL
1159
1160 # {{{ sub _Set
1161 sub _Set {
1162     my $self = shift;
1163
1164     unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1165         return ( 0, $self->loc('Permission Denied') );
1166     }
1167     return ( $self->SUPER::_Set(@_) );
1168 }
1169
1170 # }}}
1171
1172 # {{{ sub _Value
1173
1174 sub _Value {
1175     my $self = shift;
1176
1177     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1178         return (undef);
1179     }
1180
1181     return ( $self->__Value(@_) );
1182 }
1183
1184 # }}}
1185
1186 # {{{ sub CurrentUserHasRight
1187
1188 =head2 CurrentUserHasRight
1189
1190 Takes one argument. A textual string with the name of the right we want to check.
1191 Returns true if the current user has that right for this queue.
1192 Returns undef otherwise.
1193
1194 =cut
1195
1196 sub CurrentUserHasRight {
1197     my $self  = shift;
1198     my $right = shift;
1199
1200     return (
1201         $self->HasRight(
1202             Principal => $self->CurrentUser,
1203             Right     => "$right"
1204           )
1205     );
1206
1207 }
1208
1209 # }}}
1210
1211 # {{{ sub HasRight
1212
1213 =head2 HasRight
1214
1215 Takes a param hash with the fields 'Right' and 'Principal'.
1216 Principal defaults to the current user.
1217 Returns true if the principal has that right for this queue.
1218 Returns undef otherwise.
1219
1220 =cut
1221
1222 # TAKES: Right and optional "Principal" which defaults to the current user
1223 sub HasRight {
1224     my $self = shift;
1225     my %args = (
1226         Right     => undef,
1227         Principal => $self->CurrentUser,
1228         @_
1229     );
1230     my $principal = delete $args{'Principal'};
1231     unless ( $principal ) {
1232         $RT::Logger->error("Principal undefined in Queue::HasRight");
1233         return undef;
1234     }
1235
1236     return $principal->HasRight(
1237         %args,
1238         Object => ($self->Id ? $self : $RT::System),
1239     );
1240 }
1241
1242 # }}}
1243
1244 # }}}
1245
1246 1;