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