fix previous attempt at fixing ghost customers (from RT4 on master) which made things...
[freeside.git] / rt / lib / RT / Queue_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
6 #                                          <sales@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->SetContextObject( $self );
665         $cfs->LimitToGlobalOrObjectId( $self->Id );
666         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
667         $cfs->ApplySortOrder;
668     }
669     return ($cfs);
670 }
671
672 # }}}
673
674 # }}}
675
676
677 # {{{ Routines dealing with watchers.
678
679 # {{{ _CreateQueueGroups 
680
681 =head2 _CreateQueueGroups
682
683 Create the ticket groups and links for this ticket. 
684 This routine expects to be called from Ticket->Create _inside of a transaction_
685
686 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
687
688 It will return true on success and undef on failure.
689
690
691 =cut
692
693
694 sub _CreateQueueGroups {
695     my $self = shift;
696
697     my @types = qw(Cc AdminCc Requestor Owner);
698
699     foreach my $type (@types) {
700         my $type_obj = RT::Group->new($self->CurrentUser);
701         my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id, 
702                                                      Type => $type,
703                                                      Domain => 'RT::Queue-Role');
704         unless ($id) {
705             $RT::Logger->error("Couldn't create a Queue group of type '$type' for ticket ".
706                                $self->Id.": ".$msg);
707             return(undef);
708         }
709      }
710     return(1);
711    
712 }
713
714
715 # }}}
716
717 # {{{ sub AddWatcher
718
719 =head2 AddWatcher
720
721 AddWatcher takes a parameter hash. The keys are as follows:
722
723 Type        One of Requestor, Cc, AdminCc
724
725 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
726 Email       The email address of the new watcher. If a user with this 
727             email address can't be found, a new nonprivileged user will be created.
728
729 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.
730
731 Returns a tuple of (status/id, message).
732
733 =cut
734
735 sub AddWatcher {
736     my $self = shift;
737     my %args = (
738         Type  => undef,
739         PrincipalId => undef,
740         Email => undef,
741         @_
742     );
743
744     return ( 0, "No principal specified" )
745         unless $args{'Email'} or $args{'PrincipalId'};
746
747     if ( !$args{'PrincipalId'} && $args{'Email'} ) {
748         my $user = RT::User->new( $self->CurrentUser );
749         $user->LoadByEmail( $args{'Email'} );
750         $args{'PrincipalId'} = $user->PrincipalId if $user->id;
751     }
752
753     # {{{ Check ACLS
754     return ( $self->_AddWatcher(%args) )
755         if $self->CurrentUserHasRight('ModifyQueueWatchers');
756
757     #If the watcher we're trying to add is for the current user
758     if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
759         #  If it's an AdminCc and they don't have 
760         #   'WatchAsAdminCc' or 'ModifyTicket', bail
761         if ( defined $args{'Type'} && ($args{'Type'} eq 'AdminCc') ) {
762             return ( $self->_AddWatcher(%args) )
763                 if $self->CurrentUserHasRight('WatchAsAdminCc');
764         }
765
766         #  If it's a Requestor or Cc and they don't have
767         #   'Watch' or 'ModifyTicket', bail
768         elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
769             return ( $self->_AddWatcher(%args) )
770                 if $self->CurrentUserHasRight('Watch');
771         }
772         else {
773             $RT::Logger->warning( "$self -> AddWatcher got passed a bogus type");
774             return ( 0, $self->loc('Error in parameters to Queue->AddWatcher') );
775         }
776     }
777
778     return ( 0, $self->loc("Permission Denied") );
779 }
780
781 #This contains the meat of AddWatcher. but can be called from a routine like
782 # Create, which doesn't need the additional acl check
783 sub _AddWatcher {
784     my $self = shift;
785     my %args = (
786         Type   => undef,
787         Silent => undef,
788         PrincipalId => undef,
789         Email => undef,
790         @_
791     );
792
793
794     my $principal = RT::Principal->new( $self->CurrentUser );
795     if ( $args{'PrincipalId'} ) {
796         $principal->Load( $args{'PrincipalId'} );
797         if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
798             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'})))
799                 if RT::EmailParser->IsRTAddress( $email );
800         }
801     }
802     elsif ( $args{'Email'} ) {
803         if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
804             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'})));
805         }
806         my $user = RT::User->new($self->CurrentUser);
807         $user->LoadByEmail( $args{'Email'} );
808         $user->Load( $args{'Email'} )
809             unless $user->id;
810
811         if ( $user->Id ) { # If the user exists
812             $principal->Load( $user->PrincipalId );
813         } else {
814             # if the user doesn't exist, we need to create a new user
815             my $new_user = RT::User->new($RT::SystemUser);
816
817             my ( $Address, $Name ) =  
818                RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
819
820             my ( $Val, $Message ) = $new_user->Create(
821                 Name         => $Address,
822                 EmailAddress => $Address,
823                 RealName     => $Name,
824                 Privileged   => 0,
825                 Comments     => 'Autocreated when added as a watcher'
826             );
827             unless ($Val) {
828                 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
829                 # Deal with the race condition of two account creations at once
830                 $new_user->LoadByEmail( $args{'Email'} );
831             }
832             $principal->Load( $new_user->PrincipalId );
833         }
834     }
835     # If we can't find this watcher, we need to bail.
836     unless ( $principal->Id ) {
837         return(0, $self->loc("Could not find or create that user"));
838     }
839
840     my $group = RT::Group->new($self->CurrentUser);
841     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
842     unless ($group->id) {
843         return(0,$self->loc("Group not found"));
844     }
845
846     if ( $group->HasMember( $principal)) {
847
848         return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
849     }
850
851
852     my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
853     unless ($m_id) {
854         $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
855
856         return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
857     }
858     return ( 1, $self->loc('Added principal as a [_1] for this queue', $args{'Type'}) );
859 }
860
861 # }}}
862
863 # {{{ sub DeleteWatcher
864
865 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
866
867
868 Deletes a queue  watcher.  Takes two arguments:
869
870 Type  (one of Requestor,Cc,AdminCc)
871
872 and one of
873
874 PrincipalId (an RT::Principal Id of the watcher you want to remove)
875     OR
876 Email (the email address of an existing wathcer)
877
878
879 =cut
880
881
882 sub DeleteWatcher {
883     my $self = shift;
884
885     my %args = ( Type => undef,
886                  PrincipalId => undef,
887                  Email => undef,
888                  @_ );
889
890     unless ( $args{'PrincipalId'} || $args{'Email'} ) {
891         return ( 0, $self->loc("No principal specified") );
892     }
893
894     if ( !$args{PrincipalId} and $args{Email} ) {
895         my $user = RT::User->new( $self->CurrentUser );
896         my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
897         $args{PrincipalId} = $user->PrincipalId if $rv;
898     }
899     
900     my $principal = RT::Principal->new( $self->CurrentUser );
901     if ( $args{'PrincipalId'} ) {
902         $principal->Load( $args{'PrincipalId'} );
903     }
904     else {
905         my $user = RT::User->new( $self->CurrentUser );
906         $user->LoadByEmail( $args{'Email'} );
907         $principal->Load( $user->Id );
908     }
909
910     # If we can't find this watcher, we need to bail.
911     unless ( $principal->Id ) {
912         return ( 0, $self->loc("Could not find that principal") );
913     }
914
915     my $group = RT::Group->new($self->CurrentUser);
916     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
917     unless ($group->id) {
918         return(0,$self->loc("Group not found"));
919     }
920
921     my $can_modify_queue = $self->CurrentUserHasRight('ModifyQueueWatchers');
922
923     # {{{ Check ACLS
924     #If the watcher we're trying to add is for the current user
925     if ( defined $args{'PrincipalId'} and $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
926         #  If it's an AdminCc and they don't have 
927         #   'WatchAsAdminCc' or 'ModifyQueue', bail
928         if ( $args{'Type'} eq 'AdminCc' ) {
929             unless ( $can_modify_queue
930                 or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
931                 return ( 0, $self->loc('Permission Denied'))
932             }
933         }
934
935         #  If it's a Requestor or Cc and they don't have
936         #   'Watch' or 'ModifyQueue', bail
937         elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
938             unless ( $can_modify_queue
939                 or $self->CurrentUserHasRight('Watch') ) {
940                 return ( 0, $self->loc('Permission Denied'))
941             }
942         }
943         else {
944             $RT::Logger->warning( "$self -> DeleteWatcher got passed a bogus type");
945             return ( 0, $self->loc('Error in parameters to Queue->DeleteWatcher') );
946         }
947     }
948
949     # If the watcher isn't the current user 
950     # and the current user  doesn't have 'ModifyQueueWathcers' bail
951     else {
952         unless ( $can_modify_queue ) {
953             return ( 0, $self->loc("Permission Denied") );
954         }
955     }
956
957     # }}}
958
959
960     # see if this user is already a watcher.
961
962     unless ( $group->HasMember($principal)) {
963         return ( 0,
964         $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
965     }
966
967     my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
968     unless ($m_id) {
969         $RT::Logger->error("Failed to delete ".$principal->Id.
970                            " as a member of group ".$group->Id.": ".$m_msg);
971
972         return ( 0,    $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
973     }
974
975     return ( 1, $self->loc("[_1] is no longer a [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
976 }
977
978 # }}}
979
980 # {{{ AdminCcAddresses
981
982 =head2 AdminCcAddresses
983
984 returns String: All queue AdminCc email addresses as a string
985
986 =cut
987
988 sub AdminCcAddresses {
989     my $self = shift;
990     
991     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
992         return undef;
993     }   
994     
995     return ( $self->AdminCc->MemberEmailAddressesAsString )
996     
997 }   
998
999 # }}}
1000
1001 # {{{ CcAddresses
1002
1003 =head2 CcAddresses
1004
1005 returns String: All queue Ccs as a string of email addresses
1006
1007 =cut
1008
1009 sub CcAddresses {
1010     my $self = shift;
1011
1012     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1013         return undef;
1014     }
1015
1016     return ( $self->Cc->MemberEmailAddressesAsString);
1017
1018 }
1019 # }}}
1020
1021
1022 # {{{ sub Cc
1023
1024 =head2 Cc
1025
1026 Takes nothing.
1027 Returns an RT::Group object which contains this Queue's Ccs.
1028 If the user doesn't have "ShowQueue" permission, returns an empty group
1029
1030 =cut
1031
1032 sub Cc {
1033     my $self = shift;
1034
1035     my $group = RT::Group->new($self->CurrentUser);
1036     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1037         $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1038     }
1039     return ($group);
1040
1041 }
1042
1043 # }}}
1044
1045 # {{{ sub AdminCc
1046
1047 =head2 AdminCc
1048
1049 Takes nothing.
1050 Returns an RT::Group object which contains this Queue's AdminCcs.
1051 If the user doesn't have "ShowQueue" permission, returns an empty group
1052
1053 =cut
1054
1055 sub AdminCc {
1056     my $self = shift;
1057
1058     my $group = RT::Group->new($self->CurrentUser);
1059     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1060         $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1061     }
1062     return ($group);
1063
1064 }
1065
1066 # }}}
1067
1068 # {{{ IsWatcher, IsCc, IsAdminCc
1069
1070 # {{{ sub IsWatcher
1071 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1072
1073 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1074
1075 Takes a param hash with the attributes Type and PrincipalId
1076
1077 Type is one of Requestor, Cc, AdminCc and Owner
1078
1079 PrincipalId is an RT::Principal id 
1080
1081 Returns true if that principal is a member of the group Type for this queue
1082
1083
1084 =cut
1085
1086 sub IsWatcher {
1087     my $self = shift;
1088
1089     my %args = ( Type  => 'Cc',
1090         PrincipalId    => undef,
1091         @_
1092     );
1093
1094     # Load the relevant group. 
1095     my $group = RT::Group->new($self->CurrentUser);
1096     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1097     # Ask if it has the member in question
1098
1099     my $principal = RT::Principal->new($self->CurrentUser);
1100     $principal->Load($args{'PrincipalId'});
1101     unless ($principal->Id) {
1102         return (undef);
1103     }
1104
1105     return ($group->HasMemberRecursively($principal));
1106 }
1107
1108 # }}}
1109
1110
1111 # {{{ sub IsCc
1112
1113 =head2 IsCc PRINCIPAL_ID
1114
1115 Takes an RT::Principal id.
1116 Returns true if the principal is a requestor of the current queue.
1117
1118
1119 =cut
1120
1121 sub IsCc {
1122     my $self = shift;
1123     my $cc   = shift;
1124
1125     return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1126
1127 }
1128
1129 # }}}
1130
1131 # {{{ sub IsAdminCc
1132
1133 =head2 IsAdminCc PRINCIPAL_ID
1134
1135 Takes an RT::Principal id.
1136 Returns true if the principal is a requestor of the current queue.
1137
1138 =cut
1139
1140 sub IsAdminCc {
1141     my $self   = shift;
1142     my $person = shift;
1143
1144     return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1145
1146 }
1147
1148 # }}}
1149
1150
1151 # }}}
1152
1153
1154
1155
1156
1157 # }}}
1158
1159 # {{{ ACCESS CONTROL
1160
1161 # {{{ sub _Set
1162 sub _Set {
1163     my $self = shift;
1164
1165     unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1166         return ( 0, $self->loc('Permission Denied') );
1167     }
1168     return ( $self->SUPER::_Set(@_) );
1169 }
1170
1171 # }}}
1172
1173 # {{{ sub _Value
1174
1175 sub _Value {
1176     my $self = shift;
1177
1178     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1179         return (undef);
1180     }
1181
1182     return ( $self->__Value(@_) );
1183 }
1184
1185 # }}}
1186
1187 # {{{ sub CurrentUserHasRight
1188
1189 =head2 CurrentUserHasRight
1190
1191 Takes one argument. A textual string with the name of the right we want to check.
1192 Returns true if the current user has that right for this queue.
1193 Returns undef otherwise.
1194
1195 =cut
1196
1197 sub CurrentUserHasRight {
1198     my $self  = shift;
1199     my $right = shift;
1200
1201     return (
1202         $self->HasRight(
1203             Principal => $self->CurrentUser,
1204             Right     => "$right"
1205           )
1206     );
1207
1208 }
1209
1210 # }}}
1211
1212 =head2 CurrentUserCanSee
1213
1214 Returns true if the current user can see the queue, using SeeQueue
1215
1216 =cut
1217
1218 sub CurrentUserCanSee {
1219     my $self = shift;
1220
1221     return $self->CurrentUserHasRight('SeeQueue');
1222 }
1223
1224 # {{{ sub HasRight
1225
1226 =head2 HasRight
1227
1228 Takes a param hash with the fields 'Right' and 'Principal'.
1229 Principal defaults to the current user.
1230 Returns true if the principal has that right for this queue.
1231 Returns undef otherwise.
1232
1233 =cut
1234
1235 # TAKES: Right and optional "Principal" which defaults to the current user
1236 sub HasRight {
1237     my $self = shift;
1238     my %args = (
1239         Right     => undef,
1240         Principal => $self->CurrentUser,
1241         @_
1242     );
1243     my $principal = delete $args{'Principal'};
1244     unless ( $principal ) {
1245         $RT::Logger->error("Principal undefined in Queue::HasRight");
1246         return undef;
1247     }
1248
1249     return $principal->HasRight(
1250         %args,
1251         Object => ($self->Id ? $self : $RT::System),
1252     );
1253 }
1254
1255 # }}}
1256
1257 # }}}
1258
1259 1;