Removed LDAP-lookup loop for new external users.
[usit-rt.git] / lib / RT / Queue.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2012 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 use warnings;
70 use base 'RT::Record';
71
72 sub Table {'Queues'}
73
74
75
76 use RT::Groups;
77 use RT::ACL;
78 use RT::Interface::Email;
79
80 our @DEFAULT_ACTIVE_STATUS = qw(new open stalled);
81 our @DEFAULT_INACTIVE_STATUS = qw(resolved rejected deleted);  
82
83 # $self->loc('new'); # For the string extractor to get a string to localize
84 # $self->loc('open'); # For the string extractor to get a string to localize
85 # $self->loc('stalled'); # For the string extractor to get a string to localize
86 # $self->loc('resolved'); # For the string extractor to get a string to localize
87 # $self->loc('rejected'); # For the string extractor to get a string to localize
88 # $self->loc('deleted'); # For the string extractor to get a string to localize
89
90
91 our $RIGHTS = {
92     SeeQueue            => 'View queue',                                                # loc_pair
93     AdminQueue          => 'Create, modify and delete queue',                           # loc_pair
94     ShowACL             => 'Display Access Control List',                               # loc_pair
95     ModifyACL           => 'Create, modify and delete Access Control List entries',     # loc_pair
96     ModifyQueueWatchers => 'Modify queue watchers',                                     # loc_pair
97     SeeCustomField      => 'View custom field values',                                  # loc_pair
98     ModifyCustomField   => 'Modify custom field values',                                # loc_pair
99     AssignCustomFields  => 'Assign and remove queue custom fields',                     # loc_pair
100     ModifyTemplate      => 'Modify Scrip templates',                                    # loc_pair
101     ShowTemplate        => 'View Scrip templates',                                      # loc_pair
102
103     ModifyScrips        => 'Modify Scrips',                                             # loc_pair
104     ShowScrips          => 'View Scrips',                                               # loc_pair
105
106     ShowTicket          => 'View ticket summaries',                                     # loc_pair
107     ShowTicketComments  => 'View ticket private commentary',                            # loc_pair
108     ShowOutgoingEmail   => 'View exact outgoing email messages and their recipients',   # loc_pair
109
110     Watch               => 'Sign up as a ticket Requestor or ticket or queue Cc',       # loc_pair
111     WatchAsAdminCc      => 'Sign up as a ticket or queue AdminCc',                      # loc_pair
112     CreateTicket        => 'Create tickets',                                            # loc_pair
113     ReplyToTicket       => 'Reply to tickets',                                          # loc_pair
114     CommentOnTicket     => 'Comment on tickets',                                        # loc_pair
115     OwnTicket           => 'Own tickets',                                               # loc_pair
116     ModifyTicket        => 'Modify tickets',                                            # loc_pair
117     DeleteTicket        => 'Delete tickets',                                            # loc_pair
118     TakeTicket          => 'Take tickets',                                              # loc_pair
119     StealTicket         => 'Steal tickets',                                             # loc_pair
120
121     ForwardMessage      => 'Forward messages outside of RT',                            # loc_pair
122 };
123
124 our $RIGHT_CATEGORIES = {
125     SeeQueue            => 'General',
126     AdminQueue          => 'Admin',
127     ShowACL             => 'Admin',
128     ModifyACL           => 'Admin',
129     ModifyQueueWatchers => 'Admin',
130     SeeCustomField      => 'General',
131     ModifyCustomField   => 'Staff',
132     AssignCustomFields  => 'Admin',
133     ModifyTemplate      => 'Admin',
134     ShowTemplate        => 'Admin',
135     ModifyScrips        => 'Admin',
136     ShowScrips          => 'Admin',
137     ShowTicket          => 'General',
138     ShowTicketComments  => 'Staff',
139     ShowOutgoingEmail   => 'Staff',
140     Watch               => 'General',
141     WatchAsAdminCc      => 'Staff',
142     CreateTicket        => 'General',
143     ReplyToTicket       => 'General',
144     CommentOnTicket     => 'General',
145     OwnTicket           => 'Staff',
146     ModifyTicket        => 'Staff',
147     DeleteTicket        => 'Staff',
148     TakeTicket          => 'Staff',
149     StealTicket         => 'Staff',
150     ForwardMessage      => 'Staff',
151 };
152
153 # Tell RT::ACE that this sort of object can get acls granted
154 $RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
155
156 # TODO: This should be refactored out into an RT::ACLedObject or something
157 # stuff the rights into a hash of rights that can exist.
158
159 __PACKAGE__->AddRights(%$RIGHTS);
160 __PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
161 require RT::Lifecycle;
162
163 =head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
164
165 Adds the given rights to the list of possible rights.  This method
166 should be called during server startup, not at runtime.
167
168 =cut
169
170 sub AddRights {
171     my $self = shift;
172     my %new = @_;
173     $RIGHTS = { %$RIGHTS, %new };
174     %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
175                                       map { lc($_) => $_ } keys %new);
176 }
177
178 =head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
179
180 Adds the given right and category pairs to the list of right categories.  This
181 method should be called during server startup, not at runtime.
182
183 =cut
184
185 sub AddRightCategories {
186     my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
187     my %new = @_;
188     $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
189 }
190
191 sub AddLink {
192     my $self = shift;
193     my %args = ( Target => '',
194                  Base   => '',
195                  Type   => '',
196                  Silent => undef,
197                  @_ );
198
199     unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
200         return ( 0, $self->loc("Permission Denied") );
201     }
202
203     return $self->SUPER::_AddLink(%args);
204 }
205
206 sub DeleteLink {
207     my $self = shift;
208     my %args = (
209         Base   => undef,
210         Target => undef,
211         Type   => undef,
212         @_
213     );
214
215     #check acls
216     unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
217         $RT::Logger->debug("No permission to delete links");
218         return ( 0, $self->loc('Permission Denied'))
219     }
220
221     return $self->SUPER::_DeleteLink(%args);
222 }
223
224 =head2 AvailableRights
225
226 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
227
228 =cut
229
230 sub AvailableRights {
231     my $self = shift;
232     return($RIGHTS);
233 }
234
235 =head2 RightCategories
236
237 Returns a hashref where the keys are rights for this type of object and the
238 values are the category (General, Staff, Admin) the right falls into.
239
240 =cut
241
242 sub RightCategories {
243     return $RIGHT_CATEGORIES;
244 }
245
246
247 sub Lifecycle {
248     my $self = shift;
249     unless (ref $self && $self->id) { 
250         return RT::Lifecycle->Load('')
251     }
252
253     my $name = $self->_Value( Lifecycle => @_ );
254     $name ||= 'default';
255
256     my $res = RT::Lifecycle->Load( $name );
257     unless ( $res ) {
258         $RT::Logger->error("Lifecycle '$name' for queue '".$self->Name."' doesn't exist");
259         return RT::Lifecycle->Load('default');
260     }
261     return $res;
262 }
263
264 sub SetLifecycle {
265     my $self = shift;
266     my $value = shift;
267
268     if ( $value && $value ne 'default' ) {
269         return (0, $self->loc('[_1] is not valid lifecycle', $value ))
270             unless $self->ValidateLifecycle( $value );
271     } else {
272         $value = undef;
273     }
274
275     return $self->_Set( Field => 'Lifecycle', Value => $value, @_ );
276 }
277
278 =head2 ValidateLifecycle NAME
279
280 Takes a lifecycle name. Returns true if it's an ok name and such
281 lifecycle is configured. Returns undef otherwise.
282
283 =cut
284
285 sub ValidateLifecycle {
286     my $self = shift;
287     my $value = shift;
288     return undef unless RT::Lifecycle->Load( $value );
289     return 1;
290 }
291
292
293 =head2 ActiveStatusArray
294
295 Returns an array of all ActiveStatuses for this queue
296
297 =cut
298
299 sub ActiveStatusArray {
300     my $self = shift;
301     return $self->Lifecycle->Valid('initial', 'active');
302 }
303
304 =head2 InactiveStatusArray
305
306 Returns an array of all InactiveStatuses for this queue
307
308 =cut
309
310 sub InactiveStatusArray {
311     my $self = shift;
312     return $self->Lifecycle->Inactive;
313 }
314
315 =head2 StatusArray
316
317 Returns an array of all statuses for this queue
318
319 =cut
320
321 sub StatusArray {
322     my $self = shift;
323     return $self->Lifecycle->Valid( @_ );
324 }
325
326 =head2 IsValidStatus value
327
328 Returns true if value is a valid status.  Otherwise, returns 0.
329
330 =cut
331
332 sub IsValidStatus {
333     my $self  = shift;
334     return $self->Lifecycle->IsValid( shift );
335 }
336
337 =head2 IsActiveStatus value
338
339 Returns true if value is a Active status.  Otherwise, returns 0
340
341 =cut
342
343 sub IsActiveStatus {
344     my $self  = shift;
345     return $self->Lifecycle->IsValid( shift, 'initial', 'active');
346 }
347
348
349
350 =head2 IsInactiveStatus value
351
352 Returns true if value is a Inactive status.  Otherwise, returns 0
353
354
355 =cut
356
357 sub IsInactiveStatus {
358     my $self  = shift;
359     return $self->Lifecycle->IsInactive( shift );
360 }
361
362
363
364
365
366
367 =head2 Create(ARGS)
368
369 Arguments: ARGS is a hash of named parameters.  Valid parameters are:
370
371   Name (required)
372   Description
373   CorrespondAddress
374   CommentAddress
375   InitialPriority
376   FinalPriority
377   DefaultDueIn
378  
379 If you pass the ACL check, it creates the queue and returns its queue id.
380
381
382 =cut
383
384 sub Create {
385     my $self = shift;
386     my %args = (
387         Name              => undef,
388         Description       => '',
389         CorrespondAddress => '',
390         CommentAddress    => '',
391         Lifecycle         => 'default',
392         SubjectTag        => undef,
393         InitialPriority   => 0,
394         FinalPriority     => 0,
395         DefaultDueIn      => 0,
396         Sign              => undef,
397         Encrypt           => undef,
398         _RecordTransaction => 1,
399         @_
400     );
401
402     unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
403     {    #Check them ACLs
404         return ( 0, $self->loc("No permission to create queues") );
405     }
406
407     {
408         my ($val, $msg) = $self->_ValidateName( $args{'Name'} );
409         return ($val, $msg) unless $val;
410     }
411
412     if ( $args{'Lifecycle'} && $args{'Lifecycle'} ne 'default' ) {
413         return ( 0, $self->loc('Invalid lifecycle name') )
414             unless $self->ValidateLifecycle( $args{'Lifecycle'} );
415     } else {
416         $args{'Lifecycle'} = undef;
417     }
418
419     my %attrs = map {$_ => 1} $self->ReadableAttributes;
420
421     #TODO better input validation
422     $RT::Handle->BeginTransaction();
423     my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs );
424     unless ($id) {
425         $RT::Handle->Rollback();
426         return ( 0, $self->loc('Queue could not be created') );
427     }
428
429     my $create_ret = $self->_CreateQueueGroups();
430     unless ($create_ret) {
431         $RT::Handle->Rollback();
432         return ( 0, $self->loc('Queue could not be created') );
433     }
434     if ( $args{'_RecordTransaction'} ) {
435         $self->_NewTransaction( Type => "Create" );
436     }
437     $RT::Handle->Commit;
438
439     if ( defined $args{'Sign'} ) {
440         my ($status, $msg) = $self->SetSign( $args{'Sign'} );
441         $RT::Logger->error("Couldn't set attribute 'Sign': $msg")
442             unless $status;
443     }
444     if ( defined $args{'Encrypt'} ) {
445         my ($status, $msg) = $self->SetEncrypt( $args{'Encrypt'} );
446         $RT::Logger->error("Couldn't set attribute 'Encrypt': $msg")
447             unless $status;
448     }
449
450     RT->System->QueueCacheNeedsUpdate(1);
451
452     return ( $id, $self->loc("Queue created") );
453 }
454
455
456
457 sub Delete {
458     my $self = shift;
459     return ( 0,
460         $self->loc('Deleting this object would break referential integrity') );
461 }
462
463
464
465 =head2 SetDisabled
466
467 Takes a boolean.
468 1 will cause this queue to no longer be available for tickets.
469 0 will re-enable this queue.
470
471 =cut
472
473 sub SetDisabled {
474     my $self = shift;
475     my $val = shift;
476
477     $RT::Handle->BeginTransaction();
478     my $set_err = $self->_Set( Field =>'Disabled', Value => $val);
479     unless ($set_err) {
480         $RT::Handle->Rollback();
481         $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." queue ".$self->PrincipalObj->Id);
482         return (undef);
483     }
484     $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
485
486     $RT::Handle->Commit();
487
488     RT->System->QueueCacheNeedsUpdate(1);
489
490     if ( $val == 1 ) {
491         return (1, $self->loc("Queue disabled"));
492     } else {
493         return (1, $self->loc("Queue enabled"));
494     }
495
496 }
497
498
499
500 =head2 Load
501
502 Takes either a numerical id or a textual Name and loads the specified queue.
503
504 =cut
505
506 sub Load {
507     my $self = shift;
508
509     my $identifier = shift;
510     if ( !$identifier ) {
511         return (undef);
512     }
513
514     if ( $identifier =~ /^(\d+)$/ ) {
515         $self->SUPER::LoadById($identifier);
516     }
517     else {
518         $self->LoadByCols( Name => $identifier );
519     }
520
521     return ( $self->Id );
522
523 }
524
525
526
527 =head2 ValidateName NAME
528
529 Takes a queue name. Returns true if it's an ok name for
530 a new queue. Returns undef if there's already a queue by that name.
531
532 =cut
533
534 sub ValidateName {
535     my $self = shift;
536     my $name = shift;
537
538     my ($ok, $msg) = $self->_ValidateName($name);
539
540     return $ok ? 1 : 0;
541 }
542
543 sub _ValidateName {
544     my $self = shift;
545     my $name = shift;
546
547     return (undef, "Queue name is required") unless length $name;
548
549     # Validate via the superclass first
550     # Case: short circuit if it's an integer so we don't have
551     # fale negatives when loading a temp queue
552     unless ( my $q = $self->SUPER::ValidateName($name) ) {
553         return ($q, $self->loc("'[_1]' is not a valid name.", $name));
554     }
555
556     my $tempqueue = RT::Queue->new(RT->SystemUser);
557     $tempqueue->Load($name);
558
559     #If this queue exists, return undef
560     if ( $tempqueue->Name() && $tempqueue->id != $self->id)  {
561         return (undef, $self->loc("Queue already exists") );
562     }
563
564     return (1);
565 }
566
567
568 =head2 SetSign
569
570 =cut
571
572 sub Sign {
573     my $self = shift;
574     my $value = shift;
575
576     return undef unless $self->CurrentUserHasRight('SeeQueue');
577     my $attr = $self->FirstAttribute('Sign') or return 0;
578     return $attr->Content;
579 }
580
581 sub SetSign {
582     my $self = shift;
583     my $value = shift;
584
585     return ( 0, $self->loc('Permission Denied') )
586         unless $self->CurrentUserHasRight('AdminQueue');
587
588     my ($status, $msg) = $self->SetAttribute(
589         Name        => 'Sign',
590         Description => 'Sign outgoing messages by default',
591         Content     => $value,
592     );
593     return ($status, $msg) unless $status;
594     return ($status, $self->loc('Signing enabled')) if $value;
595     return ($status, $self->loc('Signing disabled'));
596 }
597
598 sub Encrypt {
599     my $self = shift;
600     my $value = shift;
601
602     return undef unless $self->CurrentUserHasRight('SeeQueue');
603     my $attr = $self->FirstAttribute('Encrypt') or return 0;
604     return $attr->Content;
605 }
606
607 sub SetEncrypt {
608     my $self = shift;
609     my $value = shift;
610
611     return ( 0, $self->loc('Permission Denied') )
612         unless $self->CurrentUserHasRight('AdminQueue');
613
614     my ($status, $msg) = $self->SetAttribute(
615         Name        => 'Encrypt',
616         Description => 'Encrypt outgoing messages by default',
617         Content     => $value,
618     );
619     return ($status, $msg) unless $status;
620     return ($status, $self->loc('Encrypting enabled')) if $value;
621     return ($status, $self->loc('Encrypting disabled'));
622 }
623
624 =head2 Templates
625
626 Returns an RT::Templates object of all of this queue's templates.
627
628 =cut
629
630 sub Templates {
631     my $self = shift;
632
633     my $templates = RT::Templates->new( $self->CurrentUser );
634
635     if ( $self->CurrentUserHasRight('ShowTemplate') ) {
636         $templates->LimitToQueue( $self->id );
637     }
638
639     return ($templates);
640 }
641
642
643
644
645 =head2 CustomField NAME
646
647 Load the queue-specific custom field named NAME
648
649 =cut
650
651 sub CustomField {
652     my $self = shift;
653     my $name = shift;
654     my $cf = RT::CustomField->new($self->CurrentUser);
655     $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id); 
656     return ($cf);
657 }
658
659
660
661 =head2 TicketCustomFields
662
663 Returns an L<RT::CustomFields> object containing all global and
664 queue-specific B<ticket> custom fields.
665
666 =cut
667
668 sub TicketCustomFields {
669     my $self = shift;
670
671     my $cfs = RT::CustomFields->new( $self->CurrentUser );
672     if ( $self->CurrentUserHasRight('SeeQueue') ) {
673         $cfs->SetContextObject( $self );
674         $cfs->LimitToGlobalOrObjectId( $self->Id );
675         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' );
676         $cfs->ApplySortOrder;
677     }
678     return ($cfs);
679 }
680
681
682
683 =head2 TicketTransactionCustomFields
684
685 Returns an L<RT::CustomFields> object containing all global and
686 queue-specific B<transaction> custom fields.
687
688 =cut
689
690 sub TicketTransactionCustomFields {
691     my $self = shift;
692
693     my $cfs = RT::CustomFields->new( $self->CurrentUser );
694     if ( $self->CurrentUserHasRight('SeeQueue') ) {
695         $cfs->SetContextObject( $self );
696         $cfs->LimitToGlobalOrObjectId( $self->Id );
697         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
698         $cfs->ApplySortOrder;
699     }
700     return ($cfs);
701 }
702
703
704
705
706
707 =head2 AllRoleGroupTypes
708
709 Returns a list of the names of the various role group types that this queue
710 has, including Requestor and Owner. If you don't want them, see
711 L</ManageableRoleGroupTypes>.
712
713 =cut
714
715 sub AllRoleGroupTypes {
716     my $self = shift;
717     return ($self->ManageableRoleGroupTypes, qw(Requestor Owner));
718 }
719
720 =head2 IsRoleGroupType
721
722 Returns whether the passed-in type is a role group type.
723
724 =cut
725
726 sub IsRoleGroupType {
727     my $self = shift;
728     my $type = shift;
729
730     for my $valid_type ($self->AllRoleGroupTypes) {
731         return 1 if $type eq $valid_type;
732     }
733
734     return 0;
735 }
736
737 =head2 ManageableRoleGroupTypes
738
739 Returns a list of the names of the various role group types that this queue
740 has, excluding Requestor and Owner. If you want them, see L</AllRoleGroupTypes>.
741
742 =cut
743
744 sub ManageableRoleGroupTypes {
745     return qw(Cc AdminCc);
746 }
747
748 =head2 IsManageableRoleGroupType
749
750 Returns whether the passed-in type is a manageable role group type.
751
752 =cut
753
754 sub IsManageableRoleGroupType {
755     my $self = shift;
756     my $type = shift;
757
758     for my $valid_type ($self->ManageableRoleGroupTypes) {
759         return 1 if $type eq $valid_type;
760     }
761
762     return 0;
763 }
764
765
766 =head2 _CreateQueueGroups
767
768 Create the ticket groups and links for this ticket. 
769 This routine expects to be called from Ticket->Create _inside of a transaction_
770
771 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
772
773 It will return true on success and undef on failure.
774
775
776 =cut
777
778 sub _CreateQueueGroups {
779     my $self = shift;
780
781     my @types = $self->AllRoleGroupTypes;
782
783     foreach my $type (@types) {
784         my $ok = $self->_CreateQueueRoleGroup($type);
785         return undef if !$ok;
786     }
787
788     return 1;
789 }
790
791 sub _CreateQueueRoleGroup {
792     my $self = shift;
793     my $type = shift;
794
795     my $type_obj = RT::Group->new($self->CurrentUser);
796     my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id, 
797                                                     Type => $type,
798                                                     Domain => 'RT::Queue-Role');
799     unless ($id) {
800         $RT::Logger->error("Couldn't create a Queue group of type '$type' for queue ".
801                             $self->Id.": ".$msg);
802         return(undef);
803     }
804
805     return $id;
806 }
807
808
809
810 # _HasModifyWatcherRight {{{
811 sub _HasModifyWatcherRight {
812     my $self = shift;
813     my %args = (
814         Type  => undef,
815         PrincipalId => undef,
816         Email => undef,
817         @_
818     );
819
820     return 1 if $self->CurrentUserHasRight('ModifyQueueWatchers');
821
822     #If the watcher we're trying to add is for the current user
823     if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
824         if ( $args{'Type'} eq 'AdminCc' ) {
825             return 1 if $self->CurrentUserHasRight('WatchAsAdminCc');
826         }
827         elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
828             return 1 if $self->CurrentUserHasRight('Watch');
829         }
830         else {
831             $RT::Logger->warning( "$self -> _HasModifyWatcher got passed a bogus type $args{Type}");
832             return ( 0, $self->loc('Invalid queue role group type [_1]', $args{Type}) );
833         }
834     }
835
836     return ( 0, $self->loc("Permission Denied") );
837 }
838
839
840 =head2 AddWatcher
841
842 AddWatcher takes a parameter hash. The keys are as follows:
843
844 Type        One of Requestor, Cc, AdminCc
845
846 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
847 Email       The email address of the new watcher. If a user with this 
848             email address can't be found, a new nonprivileged user will be created.
849
850 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.
851
852 Returns a tuple of (status/id, message).
853
854 =cut
855
856 sub AddWatcher {
857     my $self = shift;
858     my %args = (
859         Type  => undef,
860         PrincipalId => undef,
861         Email => undef,
862         @_
863     );
864
865     return ( 0, "No principal specified" )
866         unless $args{'Email'} or $args{'PrincipalId'};
867
868     if ( !$args{'PrincipalId'} && $args{'Email'} ) {
869         my $user = RT::User->new( $self->CurrentUser );
870         $user->LoadByEmail( $args{'Email'} );
871         $args{'PrincipalId'} = $user->PrincipalId if $user->id;
872     }
873
874     return ( 0, "Unknown watcher type [_1]", $args{Type} )
875         unless $self->IsRoleGroupType($args{Type});
876
877     my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
878     return ($ok, $msg) if !$ok;
879
880     return $self->_AddWatcher(%args);
881 }
882
883 #This contains the meat of AddWatcher. but can be called from a routine like
884 # Create, which doesn't need the additional acl check
885 sub _AddWatcher {
886     my $self = shift;
887     my %args = (
888         Type   => undef,
889         Silent => undef,
890         PrincipalId => undef,
891         Email => undef,
892         @_
893     );
894
895
896     my $principal = RT::Principal->new( $self->CurrentUser );
897     if ( $args{'PrincipalId'} ) {
898         $principal->Load( $args{'PrincipalId'} );
899         if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
900             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'})))
901                 if RT::EmailParser->IsRTAddress( $email );
902         }
903     }
904     elsif ( $args{'Email'} ) {
905         if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
906             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'})));
907         }
908         my $user = RT::User->new($self->CurrentUser);
909         $user->LoadByEmail( $args{'Email'} );
910         $user->Load( $args{'Email'} )
911             unless $user->id;
912
913         if ( $user->Id ) { # If the user exists
914             $principal->Load( $user->PrincipalId );
915         } else {
916             # if the user doesn't exist, we need to create a new user
917             my $new_user = RT::User->new(RT->SystemUser);
918
919             my ( $Address, $Name ) =  
920                RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
921
922             my ( $Val, $Message ) = $new_user->Create(
923                 Name         => $Address,
924                 EmailAddress => $Address,
925                 RealName     => $Name,
926                 Privileged   => 0,
927                 Comments     => 'Autocreated when added as a watcher'
928             );
929             unless ($Val) {
930                 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
931                 # Deal with the race condition of two account creations at once
932                 $new_user->LoadByEmail( $args{'Email'} );
933             }
934             $principal->Load( $new_user->PrincipalId );
935         }
936     }
937     # If we can't find this watcher, we need to bail.
938     unless ( $principal->Id ) {
939         return(0, $self->loc("Could not find or create that user"));
940     }
941
942     my $group = RT::Group->new($self->CurrentUser);
943     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
944     unless ($group->id) {
945         return(0,$self->loc("Group not found"));
946     }
947
948     if ( $group->HasMember( $principal)) {
949
950         return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
951     }
952
953
954     my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
955     unless ($m_id) {
956         $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
957
958         return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
959     }
960     return ( 1, $self->loc("Added [_1] to members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
961 }
962
963
964
965 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
966
967
968 Deletes a queue  watcher.  Takes two arguments:
969
970 Type  (one of Requestor,Cc,AdminCc)
971
972 and one of
973
974 PrincipalId (an RT::Principal Id of the watcher you want to remove)
975     OR
976 Email (the email address of an existing wathcer)
977
978
979 =cut
980
981
982 sub DeleteWatcher {
983     my $self = shift;
984
985     my %args = ( Type => undef,
986                  PrincipalId => undef,
987                  Email => undef,
988                  @_ );
989
990     unless ( $args{'PrincipalId'} || $args{'Email'} ) {
991         return ( 0, $self->loc("No principal specified") );
992     }
993
994     if ( !$args{PrincipalId} and $args{Email} ) {
995         my $user = RT::User->new( $self->CurrentUser );
996         my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
997         $args{PrincipalId} = $user->PrincipalId if $rv;
998     }
999     
1000     my $principal = RT::Principal->new( $self->CurrentUser );
1001     if ( $args{'PrincipalId'} ) {
1002         $principal->Load( $args{'PrincipalId'} );
1003     }
1004     else {
1005         my $user = RT::User->new( $self->CurrentUser );
1006         $user->LoadByEmail( $args{'Email'} );
1007         $principal->Load( $user->Id );
1008     }
1009
1010     # If we can't find this watcher, we need to bail.
1011     unless ( $principal->Id ) {
1012         return ( 0, $self->loc("Could not find that principal") );
1013     }
1014
1015     my $group = RT::Group->new($self->CurrentUser);
1016     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
1017     unless ($group->id) {
1018         return(0,$self->loc("Group not found"));
1019     }
1020
1021     return ( 0, $self->loc('Unknown watcher type [_1]', $args{Type}) )
1022         unless $self->IsRoleGroupType($args{Type});
1023
1024     my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
1025     return ($ok, $msg) if !$ok;
1026
1027     # see if this user is already a watcher.
1028
1029     unless ( $group->HasMember($principal)) {
1030         return ( 0,
1031         $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
1032     }
1033
1034     my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
1035     unless ($m_id) {
1036         $RT::Logger->error("Failed to delete ".$principal->Id.
1037                            " as a member of group ".$group->Id.": ".$m_msg);
1038
1039         return ( 0,    $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
1040     }
1041
1042     return ( 1, $self->loc("Removed [_1] from members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
1043 }
1044
1045
1046
1047 =head2 AdminCcAddresses
1048
1049 returns String: All queue AdminCc email addresses as a string
1050
1051 =cut
1052
1053 sub AdminCcAddresses {
1054     my $self = shift;
1055     
1056     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1057         return undef;
1058     }   
1059     
1060     return ( $self->AdminCc->MemberEmailAddressesAsString )
1061     
1062 }   
1063
1064
1065
1066 =head2 CcAddresses
1067
1068 returns String: All queue Ccs as a string of email addresses
1069
1070 =cut
1071
1072 sub CcAddresses {
1073     my $self = shift;
1074
1075     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1076         return undef;
1077     }
1078
1079     return ( $self->Cc->MemberEmailAddressesAsString);
1080
1081 }
1082
1083
1084
1085 =head2 Cc
1086
1087 Takes nothing.
1088 Returns an RT::Group object which contains this Queue's Ccs.
1089 If the user doesn't have "ShowQueue" permission, returns an empty group
1090
1091 =cut
1092
1093 sub Cc {
1094     my $self = shift;
1095
1096     my $group = RT::Group->new($self->CurrentUser);
1097     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1098         $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1099     }
1100     return ($group);
1101
1102 }
1103
1104
1105
1106 =head2 AdminCc
1107
1108 Takes nothing.
1109 Returns an RT::Group object which contains this Queue's AdminCcs.
1110 If the user doesn't have "ShowQueue" permission, returns an empty group
1111
1112 =cut
1113
1114 sub AdminCc {
1115     my $self = shift;
1116
1117     my $group = RT::Group->new($self->CurrentUser);
1118     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1119         $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1120     }
1121     return ($group);
1122
1123 }
1124
1125
1126
1127 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1128
1129 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1130
1131 Takes a param hash with the attributes Type and PrincipalId
1132
1133 Type is one of Requestor, Cc, AdminCc and Owner
1134
1135 PrincipalId is an RT::Principal id 
1136
1137 Returns true if that principal is a member of the group Type for this queue
1138
1139
1140 =cut
1141
1142 sub IsWatcher {
1143     my $self = shift;
1144
1145     my %args = ( Type  => 'Cc',
1146         PrincipalId    => undef,
1147         @_
1148     );
1149
1150     # Load the relevant group. 
1151     my $group = RT::Group->new($self->CurrentUser);
1152     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1153     # Ask if it has the member in question
1154
1155     my $principal = RT::Principal->new($self->CurrentUser);
1156     $principal->Load($args{'PrincipalId'});
1157     unless ($principal->Id) {
1158         return (undef);
1159     }
1160
1161     return ($group->HasMemberRecursively($principal));
1162 }
1163
1164
1165
1166
1167 =head2 IsCc PRINCIPAL_ID
1168
1169 Takes an RT::Principal id.
1170 Returns true if the principal is a requestor of the current queue.
1171
1172
1173 =cut
1174
1175 sub IsCc {
1176     my $self = shift;
1177     my $cc   = shift;
1178
1179     return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1180
1181 }
1182
1183
1184
1185 =head2 IsAdminCc PRINCIPAL_ID
1186
1187 Takes an RT::Principal id.
1188 Returns true if the principal is a requestor of the current queue.
1189
1190 =cut
1191
1192 sub IsAdminCc {
1193     my $self   = shift;
1194     my $person = shift;
1195
1196     return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1197
1198 }
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209 sub _Set {
1210     my $self = shift;
1211
1212     unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1213         return ( 0, $self->loc('Permission Denied') );
1214     }
1215     return ( $self->SUPER::_Set(@_) );
1216 }
1217
1218
1219
1220 sub _Value {
1221     my $self = shift;
1222
1223     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1224         return (undef);
1225     }
1226
1227     return ( $self->__Value(@_) );
1228 }
1229
1230
1231
1232 =head2 CurrentUserHasRight
1233
1234 Takes one argument. A textual string with the name of the right we want to check.
1235 Returns true if the current user has that right for this queue.
1236 Returns undef otherwise.
1237
1238 =cut
1239
1240 sub CurrentUserHasRight {
1241     my $self  = shift;
1242     my $right = shift;
1243
1244     return (
1245         $self->HasRight(
1246             Principal => $self->CurrentUser,
1247             Right     => "$right"
1248           )
1249     );
1250
1251 }
1252
1253 =head2 CurrentUserCanSee
1254
1255 Returns true if the current user can see the queue, using SeeQueue
1256
1257 =cut
1258
1259 sub CurrentUserCanSee {
1260     my $self = shift;
1261
1262     return $self->CurrentUserHasRight('SeeQueue');
1263 }
1264
1265
1266 =head2 HasRight
1267
1268 Takes a param hash with the fields 'Right' and 'Principal'.
1269 Principal defaults to the current user.
1270 Returns true if the principal has that right for this queue.
1271 Returns undef otherwise.
1272
1273 =cut
1274
1275 # TAKES: Right and optional "Principal" which defaults to the current user
1276 sub HasRight {
1277     my $self = shift;
1278     my %args = (
1279         Right     => undef,
1280         Principal => $self->CurrentUser,
1281         @_
1282     );
1283     my $principal = delete $args{'Principal'};
1284     unless ( $principal ) {
1285         $RT::Logger->error("Principal undefined in Queue::HasRight");
1286         return undef;
1287     }
1288
1289     return $principal->HasRight(
1290         %args,
1291         Object => ($self->Id ? $self : $RT::System),
1292     );
1293 }
1294
1295
1296
1297
1298 =head2 id
1299
1300 Returns the current value of id. 
1301 (In the database, id is stored as int(11).)
1302
1303
1304 =cut
1305
1306
1307 =head2 Name
1308
1309 Returns the current value of Name. 
1310 (In the database, Name is stored as varchar(200).)
1311
1312
1313
1314 =head2 SetName VALUE
1315
1316
1317 Set Name to VALUE. 
1318 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1319 (In the database, Name will be stored as a varchar(200).)
1320
1321
1322 =cut
1323
1324
1325 =head2 Description
1326
1327 Returns the current value of Description. 
1328 (In the database, Description is stored as varchar(255).)
1329
1330
1331
1332 =head2 SetDescription VALUE
1333
1334
1335 Set Description to VALUE. 
1336 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1337 (In the database, Description will be stored as a varchar(255).)
1338
1339
1340 =cut
1341
1342
1343 =head2 CorrespondAddress
1344
1345 Returns the current value of CorrespondAddress. 
1346 (In the database, CorrespondAddress is stored as varchar(120).)
1347
1348
1349
1350 =head2 SetCorrespondAddress VALUE
1351
1352
1353 Set CorrespondAddress to VALUE. 
1354 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1355 (In the database, CorrespondAddress will be stored as a varchar(120).)
1356
1357
1358 =cut
1359
1360
1361 =head2 CommentAddress
1362
1363 Returns the current value of CommentAddress. 
1364 (In the database, CommentAddress is stored as varchar(120).)
1365
1366
1367
1368 =head2 SetCommentAddress VALUE
1369
1370
1371 Set CommentAddress to VALUE. 
1372 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1373 (In the database, CommentAddress will be stored as a varchar(120).)
1374
1375
1376 =cut
1377
1378
1379 =head2 Lifecycle
1380
1381 Returns the current value of Lifecycle. 
1382 (In the database, Lifecycle is stored as varchar(32).)
1383
1384
1385
1386 =head2 SetLifecycle VALUE
1387
1388
1389 Set Lifecycle to VALUE. 
1390 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1391 (In the database, Lifecycle will be stored as a varchar(32).)
1392
1393
1394 =cut
1395
1396 =head2 SubjectTag
1397
1398 Returns the current value of SubjectTag. 
1399 (In the database, SubjectTag is stored as varchar(120).)
1400
1401
1402
1403 =head2 SetSubjectTag VALUE
1404
1405
1406 Set SubjectTag to VALUE. 
1407 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1408 (In the database, SubjectTag will be stored as a varchar(120).)
1409
1410
1411 =cut
1412
1413
1414 =head2 InitialPriority
1415
1416 Returns the current value of InitialPriority. 
1417 (In the database, InitialPriority is stored as int(11).)
1418
1419
1420
1421 =head2 SetInitialPriority VALUE
1422
1423
1424 Set InitialPriority to VALUE. 
1425 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1426 (In the database, InitialPriority will be stored as a int(11).)
1427
1428
1429 =cut
1430
1431
1432 =head2 FinalPriority
1433
1434 Returns the current value of FinalPriority. 
1435 (In the database, FinalPriority is stored as int(11).)
1436
1437
1438
1439 =head2 SetFinalPriority VALUE
1440
1441
1442 Set FinalPriority to VALUE. 
1443 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1444 (In the database, FinalPriority will be stored as a int(11).)
1445
1446
1447 =cut
1448
1449
1450 =head2 DefaultDueIn
1451
1452 Returns the current value of DefaultDueIn. 
1453 (In the database, DefaultDueIn is stored as int(11).)
1454
1455
1456
1457 =head2 SetDefaultDueIn VALUE
1458
1459
1460 Set DefaultDueIn to VALUE. 
1461 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1462 (In the database, DefaultDueIn will be stored as a int(11).)
1463
1464
1465 =cut
1466
1467
1468 =head2 Creator
1469
1470 Returns the current value of Creator. 
1471 (In the database, Creator is stored as int(11).)
1472
1473
1474 =cut
1475
1476
1477 =head2 Created
1478
1479 Returns the current value of Created. 
1480 (In the database, Created is stored as datetime.)
1481
1482
1483 =cut
1484
1485
1486 =head2 LastUpdatedBy
1487
1488 Returns the current value of LastUpdatedBy. 
1489 (In the database, LastUpdatedBy is stored as int(11).)
1490
1491
1492 =cut
1493
1494
1495 =head2 LastUpdated
1496
1497 Returns the current value of LastUpdated. 
1498 (In the database, LastUpdated is stored as datetime.)
1499
1500
1501 =cut
1502
1503
1504 =head2 Disabled
1505
1506 Returns the current value of Disabled. 
1507 (In the database, Disabled is stored as smallint(6).)
1508
1509
1510
1511 =head2 SetDisabled VALUE
1512
1513
1514 Set Disabled to VALUE. 
1515 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1516 (In the database, Disabled will be stored as a smallint(6).)
1517
1518
1519 =cut
1520
1521
1522
1523 sub _CoreAccessible {
1524     {
1525      
1526         id =>
1527         {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1528         Name => 
1529         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
1530         Description => 
1531         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1532         CorrespondAddress => 
1533         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1534         CommentAddress => 
1535         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1536         SubjectTag => 
1537         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1538         Lifecycle => 
1539         {read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
1540         InitialPriority => 
1541         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1542         FinalPriority => 
1543         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1544         DefaultDueIn => 
1545         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1546         Creator => 
1547         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1548         Created => 
1549         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1550         LastUpdatedBy => 
1551         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1552         LastUpdated => 
1553         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1554         Disabled => 
1555         {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
1556
1557  }
1558 };
1559
1560
1561
1562 RT::Base->_ImportOverlays();
1563
1564 1;