a3482938f1a8a91cfe1d0cd5216ffd3424bed2eb
[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         SignAuto          => undef,
340         Encrypt           => undef,
341         _RecordTransaction => 1,
342         @_
343     );
344
345     unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
346     {    #Check them ACLs
347         return ( 0, $self->loc("No permission to create queues") );
348     }
349
350     unless ( $self->ValidateName( $args{'Name'} ) ) {
351         return ( 0, $self->loc('Queue already exists') );
352     }
353
354     my %attrs = map {$_ => 1} $self->ReadableAttributes;
355
356     #TODO better input validation
357     $RT::Handle->BeginTransaction();
358     my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs );
359     unless ($id) {
360         $RT::Handle->Rollback();
361         return ( 0, $self->loc('Queue could not be created') );
362     }
363
364     my $create_ret = $self->_CreateQueueGroups();
365     unless ($create_ret) {
366         $RT::Handle->Rollback();
367         return ( 0, $self->loc('Queue could not be created') );
368     }
369     if ( $args{'_RecordTransaction'} ) {
370         $self->_NewTransaction( Type => "Create" );
371     }
372     $RT::Handle->Commit;
373
374     for my $attr (qw/Sign SignAuto Encrypt/) {
375         next unless defined $args{$attr};
376         my $set = "Set" . $attr;
377         my ($status, $msg) = $self->$set( $args{$attr} );
378         $RT::Logger->error("Couldn't set attribute '$attr': $msg")
379             unless $status;
380     }
381
382     RT->System->QueueCacheNeedsUpdate(1);
383
384     return ( $id, $self->loc("Queue created") );
385 }
386
387 # }}}
388
389 # {{{ sub Delete 
390
391 sub Delete {
392     my $self = shift;
393     return ( 0,
394         $self->loc('Deleting this object would break referential integrity') );
395 }
396
397 # }}}
398
399 # {{{ sub SetDisabled
400
401 =head2 SetDisabled
402
403 Takes a boolean.
404 1 will cause this queue to no longer be available for tickets.
405 0 will re-enable this queue.
406
407 =cut
408
409 sub SetDisabled {
410     my $self = shift;
411     my $val = shift;
412
413     $RT::Handle->BeginTransaction();
414     my $set_err = $self->SUPER::SetDisabled($val);
415     unless ($set_err) {
416         $RT::Handle->Rollback();
417         $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." queue ".$self->PrincipalObj->Id);
418         return (undef);
419     }
420     $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
421
422     $RT::Handle->Commit();
423
424     RT->System->QueueCacheNeedsUpdate(1);
425
426     if ( $val == 1 ) {
427         return (1, $self->loc("Queue disabled"));
428     } else {
429         return (1, $self->loc("Queue enabled"));
430     }
431
432 }
433
434 # }}}
435
436 # {{{ sub Load 
437
438 =head2 Load
439
440 Takes either a numerical id or a textual Name and loads the specified queue.
441
442 =cut
443
444 sub Load {
445     my $self = shift;
446
447     my $identifier = shift;
448     if ( !$identifier ) {
449         return (undef);
450     }
451
452     if ( $identifier =~ /^(\d+)$/ ) {
453         $self->SUPER::LoadById($identifier);
454     }
455     else {
456         $self->LoadByCols( Name => $identifier );
457     }
458
459     return ( $self->Id );
460
461 }
462
463 # }}}
464
465 # {{{ sub ValidateName
466
467 =head2 ValidateName NAME
468
469 Takes a queue name. Returns true if it's an ok name for
470 a new queue. Returns undef if there's already a queue by that name.
471
472 =cut
473
474 sub ValidateName {
475     my $self = shift;
476     my $name = shift;
477
478     my $tempqueue = new RT::Queue($RT::SystemUser);
479     $tempqueue->Load($name);
480
481     #If this queue exists, return undef
482     if ( $tempqueue->Name() && $tempqueue->id != $self->id)  {
483         return (undef);
484     }
485
486     #If the queue doesn't exist, return 1
487     else {
488         return ($self->SUPER::ValidateName($name));
489     }
490
491 }
492
493 # }}}
494
495 =head2 SetSign
496
497 =cut
498
499 sub Sign {
500     my $self = shift;
501     my $value = shift;
502
503     return undef unless $self->CurrentUserHasRight('SeeQueue');
504     my $attr = $self->FirstAttribute('Sign') or return 0;
505     return $attr->Content;
506 }
507
508 sub SetSign {
509     my $self = shift;
510     my $value = shift;
511
512     return ( 0, $self->loc('Permission Denied') )
513         unless $self->CurrentUserHasRight('AdminQueue');
514
515     my ($status, $msg) = $self->SetAttribute(
516         Name        => 'Sign',
517         Description => 'Sign outgoing messages by default',
518         Content     => $value,
519     );
520     return ($status, $msg) unless $status;
521     return ($status, $self->loc('Signing enabled')) if $value;
522     return ($status, $self->loc('Signing disabled'));
523 }
524
525 sub SignAuto {
526     my $self = shift;
527     my $value = shift;
528
529     return undef unless $self->CurrentUserHasRight('SeeQueue');
530     my $attr = $self->FirstAttribute('SignAuto') or return 0;
531     return $attr->Content;
532 }
533
534 sub SetSignAuto {
535     my $self = shift;
536     my $value = shift;
537
538     return ( 0, $self->loc('Permission Denied') )
539         unless $self->CurrentUserHasRight('AdminQueue');
540
541     my ($status, $msg) = $self->SetAttribute(
542         Name        => 'SignAuto',
543         Description => 'Sign auto-generated outgoing messages',
544         Content     => $value,
545     );
546     return ($status, $msg) unless $status;
547     return ($status, $self->loc('Signing enabled')) if $value;
548     return ($status, $self->loc('Signing disabled'));
549 }
550
551 sub Encrypt {
552     my $self = shift;
553     my $value = shift;
554
555     return undef unless $self->CurrentUserHasRight('SeeQueue');
556     my $attr = $self->FirstAttribute('Encrypt') or return 0;
557     return $attr->Content;
558 }
559
560 sub SetEncrypt {
561     my $self = shift;
562     my $value = shift;
563
564     return ( 0, $self->loc('Permission Denied') )
565         unless $self->CurrentUserHasRight('AdminQueue');
566
567     my ($status, $msg) = $self->SetAttribute(
568         Name        => 'Encrypt',
569         Description => 'Encrypt outgoing messages by default',
570         Content     => $value,
571     );
572     return ($status, $msg) unless $status;
573     return ($status, $self->loc('Encrypting enabled')) if $value;
574     return ($status, $self->loc('Encrypting disabled'));
575 }
576
577 sub SubjectTag {
578     my $self = shift;
579     return RT->System->SubjectTag( $self );
580 }
581
582 sub SetSubjectTag {
583     my $self = shift;
584     my $value = shift;
585
586     return ( 0, $self->loc('Permission Denied') )
587         unless $self->CurrentUserHasRight('AdminQueue');
588
589     my $attr = RT->System->FirstAttribute('BrandedSubjectTag');
590     my $map = $attr ? $attr->Content : {};
591     if ( defined $value && length $value ) {
592         $map->{ $self->id } = $value;
593     } else {
594         delete $map->{ $self->id };
595     }
596
597     my ($status, $msg) = RT->System->SetAttribute(
598         Name        => 'BrandedSubjectTag',
599         Description => 'Queue id => subject tag map',
600         Content     => $map,
601     );
602     return ($status, $msg) unless $status;
603     return ($status, $self->loc(
604         "SubjectTag changed to [_1]", 
605         (defined $value && length $value)? $value : $self->loc("(no value)")
606     ))
607 }
608
609 # {{{ sub Templates
610
611 =head2 Templates
612
613 Returns an RT::Templates object of all of this queue's templates.
614
615 =cut
616
617 sub Templates {
618     my $self = shift;
619
620     my $templates = RT::Templates->new( $self->CurrentUser );
621
622     if ( $self->CurrentUserHasRight('ShowTemplate') ) {
623         $templates->LimitToQueue( $self->id );
624     }
625
626     return ($templates);
627 }
628
629 # }}}
630
631 # {{{ Dealing with custom fields
632
633 # {{{  CustomField
634
635 =head2 CustomField NAME
636
637 Load the queue-specific custom field named NAME
638
639 =cut
640
641 sub CustomField {
642     my $self = shift;
643     my $name = shift;
644     my $cf = RT::CustomField->new($self->CurrentUser);
645     $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id); 
646     return ($cf);
647 }
648
649
650 # {{{ TicketCustomFields
651
652 =head2 TicketCustomFields
653
654 Returns an L<RT::CustomFields> object containing all global and
655 queue-specific B<ticket> custom fields.
656
657 =cut
658
659 sub TicketCustomFields {
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' );
667         $cfs->ApplySortOrder;
668     }
669     return ($cfs);
670 }
671
672 # }}}
673
674 # {{{ TicketTransactionCustomFields
675
676 =head2 TicketTransactionCustomFields
677
678 Returns an L<RT::CustomFields> object containing all global and
679 queue-specific B<transaction> custom fields.
680
681 =cut
682
683 sub TicketTransactionCustomFields {
684     my $self = shift;
685
686     my $cfs = RT::CustomFields->new( $self->CurrentUser );
687     if ( $self->CurrentUserHasRight('SeeQueue') ) {
688         $cfs->SetContextObject( $self );
689         $cfs->LimitToGlobalOrObjectId( $self->Id );
690         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
691         $cfs->ApplySortOrder;
692     }
693     return ($cfs);
694 }
695
696 # }}}
697
698 # }}}
699
700
701 # {{{ Routines dealing with watchers.
702
703 # {{{ _CreateQueueGroups 
704
705 =head2 _CreateQueueGroups
706
707 Create the ticket groups and links for this ticket. 
708 This routine expects to be called from Ticket->Create _inside of a transaction_
709
710 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
711
712 It will return true on success and undef on failure.
713
714
715 =cut
716
717
718 sub _CreateQueueGroups {
719     my $self = shift;
720
721     my @types = qw(Cc AdminCc Requestor Owner);
722
723     foreach my $type (@types) {
724         my $type_obj = RT::Group->new($self->CurrentUser);
725         my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id, 
726                                                      Type => $type,
727                                                      Domain => 'RT::Queue-Role');
728         unless ($id) {
729             $RT::Logger->error("Couldn't create a Queue group of type '$type' for ticket ".
730                                $self->Id.": ".$msg);
731             return(undef);
732         }
733      }
734     return(1);
735    
736 }
737
738
739 # }}}
740
741 # {{{ sub AddWatcher
742
743 =head2 AddWatcher
744
745 AddWatcher takes a parameter hash. The keys are as follows:
746
747 Type        One of Requestor, Cc, AdminCc
748
749 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
750 Email       The email address of the new watcher. If a user with this 
751             email address can't be found, a new nonprivileged user will be created.
752
753 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.
754
755 Returns a tuple of (status/id, message).
756
757 =cut
758
759 sub AddWatcher {
760     my $self = shift;
761     my %args = (
762         Type  => undef,
763         PrincipalId => undef,
764         Email => undef,
765         @_
766     );
767
768     return ( 0, "No principal specified" )
769         unless $args{'Email'} or $args{'PrincipalId'};
770
771     if ( !$args{'PrincipalId'} && $args{'Email'} ) {
772         my $user = RT::User->new( $self->CurrentUser );
773         $user->LoadByEmail( $args{'Email'} );
774         $args{'PrincipalId'} = $user->PrincipalId if $user->id;
775     }
776
777     # {{{ Check ACLS
778     return ( $self->_AddWatcher(%args) )
779         if $self->CurrentUserHasRight('ModifyQueueWatchers');
780
781     #If the watcher we're trying to add is for the current user
782     if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
783         #  If it's an AdminCc and they don't have 
784         #   'WatchAsAdminCc' or 'ModifyTicket', bail
785         if ( defined $args{'Type'} && ($args{'Type'} eq 'AdminCc') ) {
786             return ( $self->_AddWatcher(%args) )
787                 if $self->CurrentUserHasRight('WatchAsAdminCc');
788         }
789
790         #  If it's a Requestor or Cc and they don't have
791         #   'Watch' or 'ModifyTicket', bail
792         elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
793             return ( $self->_AddWatcher(%args) )
794                 if $self->CurrentUserHasRight('Watch');
795         }
796         else {
797             $RT::Logger->warning( "$self -> AddWatcher got passed a bogus type");
798             return ( 0, $self->loc('Error in parameters to Queue->AddWatcher') );
799         }
800     }
801
802     return ( 0, $self->loc("Permission Denied") );
803 }
804
805 #This contains the meat of AddWatcher. but can be called from a routine like
806 # Create, which doesn't need the additional acl check
807 sub _AddWatcher {
808     my $self = shift;
809     my %args = (
810         Type   => undef,
811         Silent => undef,
812         PrincipalId => undef,
813         Email => undef,
814         @_
815     );
816
817
818     my $principal = RT::Principal->new( $self->CurrentUser );
819     if ( $args{'PrincipalId'} ) {
820         $principal->Load( $args{'PrincipalId'} );
821         if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
822             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'})))
823                 if RT::EmailParser->IsRTAddress( $email );
824         }
825     }
826     elsif ( $args{'Email'} ) {
827         if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
828             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'})));
829         }
830         my $user = RT::User->new($self->CurrentUser);
831         $user->LoadByEmail( $args{'Email'} );
832         $user->Load( $args{'Email'} )
833             unless $user->id;
834
835         if ( $user->Id ) { # If the user exists
836             $principal->Load( $user->PrincipalId );
837         } else {
838             # if the user doesn't exist, we need to create a new user
839             my $new_user = RT::User->new($RT::SystemUser);
840
841             my ( $Address, $Name ) =  
842                RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
843
844             my ( $Val, $Message ) = $new_user->Create(
845                 Name         => $Address,
846                 EmailAddress => $Address,
847                 RealName     => $Name,
848                 Privileged   => 0,
849                 Comments     => 'Autocreated when added as a watcher'
850             );
851             unless ($Val) {
852                 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
853                 # Deal with the race condition of two account creations at once
854                 $new_user->LoadByEmail( $args{'Email'} );
855             }
856             $principal->Load( $new_user->PrincipalId );
857         }
858     }
859     # If we can't find this watcher, we need to bail.
860     unless ( $principal->Id ) {
861         return(0, $self->loc("Could not find or create that user"));
862     }
863
864     my $group = RT::Group->new($self->CurrentUser);
865     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
866     unless ($group->id) {
867         return(0,$self->loc("Group not found"));
868     }
869
870     if ( $group->HasMember( $principal)) {
871
872         return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
873     }
874
875
876     my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
877     unless ($m_id) {
878         $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
879
880         return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
881     }
882     return ( 1, $self->loc('Added principal as a [_1] for this queue', $args{'Type'}) );
883 }
884
885 # }}}
886
887 # {{{ sub DeleteWatcher
888
889 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
890
891
892 Deletes a queue  watcher.  Takes two arguments:
893
894 Type  (one of Requestor,Cc,AdminCc)
895
896 and one of
897
898 PrincipalId (an RT::Principal Id of the watcher you want to remove)
899     OR
900 Email (the email address of an existing wathcer)
901
902
903 =cut
904
905
906 sub DeleteWatcher {
907     my $self = shift;
908
909     my %args = ( Type => undef,
910                  PrincipalId => undef,
911                  Email => undef,
912                  @_ );
913
914     unless ( $args{'PrincipalId'} || $args{'Email'} ) {
915         return ( 0, $self->loc("No principal specified") );
916     }
917
918     if ( !$args{PrincipalId} and $args{Email} ) {
919         my $user = RT::User->new( $self->CurrentUser );
920         my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
921         $args{PrincipalId} = $user->PrincipalId if $rv;
922     }
923     
924     my $principal = RT::Principal->new( $self->CurrentUser );
925     if ( $args{'PrincipalId'} ) {
926         $principal->Load( $args{'PrincipalId'} );
927     }
928     else {
929         my $user = RT::User->new( $self->CurrentUser );
930         $user->LoadByEmail( $args{'Email'} );
931         $principal->Load( $user->Id );
932     }
933
934     # If we can't find this watcher, we need to bail.
935     unless ( $principal->Id ) {
936         return ( 0, $self->loc("Could not find that principal") );
937     }
938
939     my $group = RT::Group->new($self->CurrentUser);
940     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
941     unless ($group->id) {
942         return(0,$self->loc("Group not found"));
943     }
944
945     my $can_modify_queue = $self->CurrentUserHasRight('ModifyQueueWatchers');
946
947     # {{{ Check ACLS
948     #If the watcher we're trying to add is for the current user
949     if ( defined $args{'PrincipalId'} and $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
950         #  If it's an AdminCc and they don't have 
951         #   'WatchAsAdminCc' or 'ModifyQueue', bail
952         if ( $args{'Type'} eq 'AdminCc' ) {
953             unless ( $can_modify_queue
954                 or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
955                 return ( 0, $self->loc('Permission Denied'))
956             }
957         }
958
959         #  If it's a Requestor or Cc and they don't have
960         #   'Watch' or 'ModifyQueue', bail
961         elsif ( ( $args{'Type'} eq 'Cc' ) or ( $args{'Type'} eq 'Requestor' ) ) {
962             unless ( $can_modify_queue
963                 or $self->CurrentUserHasRight('Watch') ) {
964                 return ( 0, $self->loc('Permission Denied'))
965             }
966         }
967         else {
968             $RT::Logger->warning( "$self -> DeleteWatcher got passed a bogus type");
969             return ( 0, $self->loc('Error in parameters to Queue->DeleteWatcher') );
970         }
971     }
972
973     # If the watcher isn't the current user 
974     # and the current user  doesn't have 'ModifyQueueWathcers' bail
975     else {
976         unless ( $can_modify_queue ) {
977             return ( 0, $self->loc("Permission Denied") );
978         }
979     }
980
981     # }}}
982
983
984     # see if this user is already a watcher.
985
986     unless ( $group->HasMember($principal)) {
987         return ( 0,
988         $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
989     }
990
991     my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
992     unless ($m_id) {
993         $RT::Logger->error("Failed to delete ".$principal->Id.
994                            " as a member of group ".$group->Id.": ".$m_msg);
995
996         return ( 0,    $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
997     }
998
999     return ( 1, $self->loc("[_1] is no longer a [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
1000 }
1001
1002 # }}}
1003
1004 # {{{ AdminCcAddresses
1005
1006 =head2 AdminCcAddresses
1007
1008 returns String: All queue AdminCc email addresses as a string
1009
1010 =cut
1011
1012 sub AdminCcAddresses {
1013     my $self = shift;
1014     
1015     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1016         return undef;
1017     }   
1018     
1019     return ( $self->AdminCc->MemberEmailAddressesAsString )
1020     
1021 }   
1022
1023 # }}}
1024
1025 # {{{ CcAddresses
1026
1027 =head2 CcAddresses
1028
1029 returns String: All queue Ccs as a string of email addresses
1030
1031 =cut
1032
1033 sub CcAddresses {
1034     my $self = shift;
1035
1036     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1037         return undef;
1038     }
1039
1040     return ( $self->Cc->MemberEmailAddressesAsString);
1041
1042 }
1043 # }}}
1044
1045
1046 # {{{ sub Cc
1047
1048 =head2 Cc
1049
1050 Takes nothing.
1051 Returns an RT::Group object which contains this Queue's Ccs.
1052 If the user doesn't have "ShowQueue" permission, returns an empty group
1053
1054 =cut
1055
1056 sub Cc {
1057     my $self = shift;
1058
1059     my $group = RT::Group->new($self->CurrentUser);
1060     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1061         $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1062     }
1063     return ($group);
1064
1065 }
1066
1067 # }}}
1068
1069 # {{{ sub AdminCc
1070
1071 =head2 AdminCc
1072
1073 Takes nothing.
1074 Returns an RT::Group object which contains this Queue's AdminCcs.
1075 If the user doesn't have "ShowQueue" permission, returns an empty group
1076
1077 =cut
1078
1079 sub AdminCc {
1080     my $self = shift;
1081
1082     my $group = RT::Group->new($self->CurrentUser);
1083     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1084         $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1085     }
1086     return ($group);
1087
1088 }
1089
1090 # }}}
1091
1092 # {{{ IsWatcher, IsCc, IsAdminCc
1093
1094 # {{{ sub IsWatcher
1095 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1096
1097 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1098
1099 Takes a param hash with the attributes Type and PrincipalId
1100
1101 Type is one of Requestor, Cc, AdminCc and Owner
1102
1103 PrincipalId is an RT::Principal id 
1104
1105 Returns true if that principal is a member of the group Type for this queue
1106
1107
1108 =cut
1109
1110 sub IsWatcher {
1111     my $self = shift;
1112
1113     my %args = ( Type  => 'Cc',
1114         PrincipalId    => undef,
1115         @_
1116     );
1117
1118     # Load the relevant group. 
1119     my $group = RT::Group->new($self->CurrentUser);
1120     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1121     # Ask if it has the member in question
1122
1123     my $principal = RT::Principal->new($self->CurrentUser);
1124     $principal->Load($args{'PrincipalId'});
1125     unless ($principal->Id) {
1126         return (undef);
1127     }
1128
1129     return ($group->HasMemberRecursively($principal));
1130 }
1131
1132 # }}}
1133
1134
1135 # {{{ sub IsCc
1136
1137 =head2 IsCc PRINCIPAL_ID
1138
1139 Takes an RT::Principal id.
1140 Returns true if the principal is a requestor of the current queue.
1141
1142
1143 =cut
1144
1145 sub IsCc {
1146     my $self = shift;
1147     my $cc   = shift;
1148
1149     return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1150
1151 }
1152
1153 # }}}
1154
1155 # {{{ sub IsAdminCc
1156
1157 =head2 IsAdminCc PRINCIPAL_ID
1158
1159 Takes an RT::Principal id.
1160 Returns true if the principal is a requestor of the current queue.
1161
1162 =cut
1163
1164 sub IsAdminCc {
1165     my $self   = shift;
1166     my $person = shift;
1167
1168     return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1169
1170 }
1171
1172 # }}}
1173
1174
1175 # }}}
1176
1177
1178
1179
1180
1181 # }}}
1182
1183 # {{{ ACCESS CONTROL
1184
1185 # {{{ sub _Set
1186 sub _Set {
1187     my $self = shift;
1188
1189     unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1190         return ( 0, $self->loc('Permission Denied') );
1191     }
1192     return ( $self->SUPER::_Set(@_) );
1193 }
1194
1195 # }}}
1196
1197 # {{{ sub _Value
1198
1199 sub _Value {
1200     my $self = shift;
1201
1202     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1203         return (undef);
1204     }
1205
1206     return ( $self->__Value(@_) );
1207 }
1208
1209 # }}}
1210
1211 # {{{ sub CurrentUserHasRight
1212
1213 =head2 CurrentUserHasRight
1214
1215 Takes one argument. A textual string with the name of the right we want to check.
1216 Returns true if the current user has that right for this queue.
1217 Returns undef otherwise.
1218
1219 =cut
1220
1221 sub CurrentUserHasRight {
1222     my $self  = shift;
1223     my $right = shift;
1224
1225     return (
1226         $self->HasRight(
1227             Principal => $self->CurrentUser,
1228             Right     => "$right"
1229           )
1230     );
1231
1232 }
1233
1234 # }}}
1235
1236 =head2 CurrentUserCanSee
1237
1238 Returns true if the current user can see the queue, using SeeQueue
1239
1240 =cut
1241
1242 sub CurrentUserCanSee {
1243     my $self = shift;
1244
1245     return $self->CurrentUserHasRight('SeeQueue');
1246 }
1247
1248 # {{{ sub HasRight
1249
1250 =head2 HasRight
1251
1252 Takes a param hash with the fields 'Right' and 'Principal'.
1253 Principal defaults to the current user.
1254 Returns true if the principal has that right for this queue.
1255 Returns undef otherwise.
1256
1257 =cut
1258
1259 # TAKES: Right and optional "Principal" which defaults to the current user
1260 sub HasRight {
1261     my $self = shift;
1262     my %args = (
1263         Right     => undef,
1264         Principal => $self->CurrentUser,
1265         @_
1266     );
1267     my $principal = delete $args{'Principal'};
1268     unless ( $principal ) {
1269         $RT::Logger->error("Principal undefined in Queue::HasRight");
1270         return undef;
1271     }
1272
1273     return $principal->HasRight(
1274         %args,
1275         Object => ($self->Id ? $self : $RT::System),
1276     );
1277 }
1278
1279 # }}}
1280
1281 # }}}
1282
1283 1;