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