starting to work...
[freeside.git] / rt / 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->LimitToGlobalOrObjectId( $self->Id );
696         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
697         $cfs->ApplySortOrder;
698     }
699     return ($cfs);
700 }
701
702
703
704
705
706 =head2 AllRoleGroupTypes
707
708 Returns a list of the names of the various role group types that this queue
709 has, including Requestor and Owner. If you don't want them, see
710 L</ManageableRoleGroupTypes>.
711
712 =cut
713
714 sub AllRoleGroupTypes {
715     my $self = shift;
716     return ($self->ManageableRoleGroupTypes, qw(Requestor Owner));
717 }
718
719 =head2 IsRoleGroupType
720
721 Returns whether the passed-in type is a role group type.
722
723 =cut
724
725 sub IsRoleGroupType {
726     my $self = shift;
727     my $type = shift;
728
729     for my $valid_type ($self->AllRoleGroupTypes) {
730         return 1 if $type eq $valid_type;
731     }
732
733     return 0;
734 }
735
736 =head2 ManageableRoleGroupTypes
737
738 Returns a list of the names of the various role group types that this queue
739 has, excluding Requestor and Owner. If you want them, see L</AllRoleGroupTypes>.
740
741 =cut
742
743 sub ManageableRoleGroupTypes {
744     return qw(Cc AdminCc);
745 }
746
747 =head2 IsManageableRoleGroupType
748
749 Returns whether the passed-in type is a manageable role group type.
750
751 =cut
752
753 sub IsManageableRoleGroupType {
754     my $self = shift;
755     my $type = shift;
756
757     for my $valid_type ($self->ManageableRoleGroupTypes) {
758         return 1 if $type eq $valid_type;
759     }
760
761     return 0;
762 }
763
764
765 =head2 _CreateQueueGroups
766
767 Create the ticket groups and links for this ticket. 
768 This routine expects to be called from Ticket->Create _inside of a transaction_
769
770 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
771
772 It will return true on success and undef on failure.
773
774
775 =cut
776
777 sub _CreateQueueGroups {
778     my $self = shift;
779
780     my @types = $self->AllRoleGroupTypes;
781
782     foreach my $type (@types) {
783         my $ok = $self->_CreateQueueRoleGroup($type);
784         return undef if !$ok;
785     }
786
787     return 1;
788 }
789
790 sub _CreateQueueRoleGroup {
791     my $self = shift;
792     my $type = shift;
793
794     my $type_obj = RT::Group->new($self->CurrentUser);
795     my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id, 
796                                                     Type => $type,
797                                                     Domain => 'RT::Queue-Role');
798     unless ($id) {
799         $RT::Logger->error("Couldn't create a Queue group of type '$type' for queue ".
800                             $self->Id.": ".$msg);
801         return(undef);
802     }
803
804     return $id;
805 }
806
807
808
809 # _HasModifyWatcherRight {{{
810 sub _HasModifyWatcherRight {
811     my $self = shift;
812     my %args = (
813         Type  => undef,
814         PrincipalId => undef,
815         Email => undef,
816         @_
817     );
818
819     return 1 if $self->CurrentUserHasRight('ModifyQueueWatchers');
820
821     #If the watcher we're trying to add is for the current user
822     if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
823         if ( $args{'Type'} eq 'AdminCc' ) {
824             return 1 if $self->CurrentUserHasRight('WatchAsAdminCc');
825         }
826         elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
827             return 1 if $self->CurrentUserHasRight('Watch');
828         }
829         else {
830             $RT::Logger->warning( "$self -> _HasModifyWatcher got passed a bogus type $args{Type}");
831             return ( 0, $self->loc('Invalid queue role group type [_1]', $args{Type}) );
832         }
833     }
834
835     return ( 0, $self->loc("Permission Denied") );
836 }
837
838
839 =head2 AddWatcher
840
841 AddWatcher takes a parameter hash. The keys are as follows:
842
843 Type        One of Requestor, Cc, AdminCc
844
845 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
846 Email       The email address of the new watcher. If a user with this 
847             email address can't be found, a new nonprivileged user will be created.
848
849 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.
850
851 Returns a tuple of (status/id, message).
852
853 =cut
854
855 sub AddWatcher {
856     my $self = shift;
857     my %args = (
858         Type  => undef,
859         PrincipalId => undef,
860         Email => undef,
861         @_
862     );
863
864     return ( 0, "No principal specified" )
865         unless $args{'Email'} or $args{'PrincipalId'};
866
867     if ( !$args{'PrincipalId'} && $args{'Email'} ) {
868         my $user = RT::User->new( $self->CurrentUser );
869         $user->LoadByEmail( $args{'Email'} );
870         $args{'PrincipalId'} = $user->PrincipalId if $user->id;
871     }
872
873     return ( 0, "Unknown watcher type [_1]", $args{Type} )
874         unless $self->IsRoleGroupType($args{Type});
875
876     my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
877     return ($ok, $msg) if !$ok;
878
879     return $self->_AddWatcher(%args);
880 }
881
882 #This contains the meat of AddWatcher. but can be called from a routine like
883 # Create, which doesn't need the additional acl check
884 sub _AddWatcher {
885     my $self = shift;
886     my %args = (
887         Type   => undef,
888         Silent => undef,
889         PrincipalId => undef,
890         Email => undef,
891         @_
892     );
893
894
895     my $principal = RT::Principal->new( $self->CurrentUser );
896     if ( $args{'PrincipalId'} ) {
897         $principal->Load( $args{'PrincipalId'} );
898         if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
899             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'})))
900                 if RT::EmailParser->IsRTAddress( $email );
901         }
902     }
903     elsif ( $args{'Email'} ) {
904         if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
905             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'})));
906         }
907         my $user = RT::User->new($self->CurrentUser);
908         $user->LoadByEmail( $args{'Email'} );
909         $user->Load( $args{'Email'} )
910             unless $user->id;
911
912         if ( $user->Id ) { # If the user exists
913             $principal->Load( $user->PrincipalId );
914         } else {
915             # if the user doesn't exist, we need to create a new user
916             my $new_user = RT::User->new(RT->SystemUser);
917
918             my ( $Address, $Name ) =  
919                RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
920
921             my ( $Val, $Message ) = $new_user->Create(
922                 Name         => $Address,
923                 EmailAddress => $Address,
924                 RealName     => $Name,
925                 Privileged   => 0,
926                 Comments     => 'Autocreated when added as a watcher'
927             );
928             unless ($Val) {
929                 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
930                 # Deal with the race condition of two account creations at once
931                 $new_user->LoadByEmail( $args{'Email'} );
932             }
933             $principal->Load( $new_user->PrincipalId );
934         }
935     }
936     # If we can't find this watcher, we need to bail.
937     unless ( $principal->Id ) {
938         return(0, $self->loc("Could not find or create that user"));
939     }
940
941     my $group = RT::Group->new($self->CurrentUser);
942     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
943     unless ($group->id) {
944         return(0,$self->loc("Group not found"));
945     }
946
947     if ( $group->HasMember( $principal)) {
948
949         return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
950     }
951
952
953     my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
954     unless ($m_id) {
955         $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
956
957         return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
958     }
959     return ( 1, $self->loc("Added [_1] to members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
960 }
961
962
963
964 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
965
966
967 Deletes a queue  watcher.  Takes two arguments:
968
969 Type  (one of Requestor,Cc,AdminCc)
970
971 and one of
972
973 PrincipalId (an RT::Principal Id of the watcher you want to remove)
974     OR
975 Email (the email address of an existing wathcer)
976
977
978 =cut
979
980
981 sub DeleteWatcher {
982     my $self = shift;
983
984     my %args = ( Type => undef,
985                  PrincipalId => undef,
986                  Email => undef,
987                  @_ );
988
989     unless ( $args{'PrincipalId'} || $args{'Email'} ) {
990         return ( 0, $self->loc("No principal specified") );
991     }
992
993     if ( !$args{PrincipalId} and $args{Email} ) {
994         my $user = RT::User->new( $self->CurrentUser );
995         my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
996         $args{PrincipalId} = $user->PrincipalId if $rv;
997     }
998     
999     my $principal = RT::Principal->new( $self->CurrentUser );
1000     if ( $args{'PrincipalId'} ) {
1001         $principal->Load( $args{'PrincipalId'} );
1002     }
1003     else {
1004         my $user = RT::User->new( $self->CurrentUser );
1005         $user->LoadByEmail( $args{'Email'} );
1006         $principal->Load( $user->Id );
1007     }
1008
1009     # If we can't find this watcher, we need to bail.
1010     unless ( $principal->Id ) {
1011         return ( 0, $self->loc("Could not find that principal") );
1012     }
1013
1014     my $group = RT::Group->new($self->CurrentUser);
1015     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
1016     unless ($group->id) {
1017         return(0,$self->loc("Group not found"));
1018     }
1019
1020     return ( 0, $self->loc('Unknown watcher type [_1]', $args{Type}) )
1021         unless $self->IsRoleGroupType($args{Type});
1022
1023     my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
1024     return ($ok, $msg) if !$ok;
1025
1026     # see if this user is already a watcher.
1027
1028     unless ( $group->HasMember($principal)) {
1029         return ( 0,
1030         $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
1031     }
1032
1033     my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
1034     unless ($m_id) {
1035         $RT::Logger->error("Failed to delete ".$principal->Id.
1036                            " as a member of group ".$group->Id.": ".$m_msg);
1037
1038         return ( 0,    $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
1039     }
1040
1041     return ( 1, $self->loc("Removed [_1] from members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
1042 }
1043
1044
1045
1046 =head2 AdminCcAddresses
1047
1048 returns String: All queue AdminCc email addresses as a string
1049
1050 =cut
1051
1052 sub AdminCcAddresses {
1053     my $self = shift;
1054     
1055     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1056         return undef;
1057     }   
1058     
1059     return ( $self->AdminCc->MemberEmailAddressesAsString )
1060     
1061 }   
1062
1063
1064
1065 =head2 CcAddresses
1066
1067 returns String: All queue Ccs as a string of email addresses
1068
1069 =cut
1070
1071 sub CcAddresses {
1072     my $self = shift;
1073
1074     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1075         return undef;
1076     }
1077
1078     return ( $self->Cc->MemberEmailAddressesAsString);
1079
1080 }
1081
1082
1083
1084 =head2 Cc
1085
1086 Takes nothing.
1087 Returns an RT::Group object which contains this Queue's Ccs.
1088 If the user doesn't have "ShowQueue" permission, returns an empty group
1089
1090 =cut
1091
1092 sub Cc {
1093     my $self = shift;
1094
1095     my $group = RT::Group->new($self->CurrentUser);
1096     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1097         $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1098     }
1099     return ($group);
1100
1101 }
1102
1103
1104
1105 =head2 AdminCc
1106
1107 Takes nothing.
1108 Returns an RT::Group object which contains this Queue's AdminCcs.
1109 If the user doesn't have "ShowQueue" permission, returns an empty group
1110
1111 =cut
1112
1113 sub AdminCc {
1114     my $self = shift;
1115
1116     my $group = RT::Group->new($self->CurrentUser);
1117     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1118         $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1119     }
1120     return ($group);
1121
1122 }
1123
1124
1125
1126 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1127
1128 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1129
1130 Takes a param hash with the attributes Type and PrincipalId
1131
1132 Type is one of Requestor, Cc, AdminCc and Owner
1133
1134 PrincipalId is an RT::Principal id 
1135
1136 Returns true if that principal is a member of the group Type for this queue
1137
1138
1139 =cut
1140
1141 sub IsWatcher {
1142     my $self = shift;
1143
1144     my %args = ( Type  => 'Cc',
1145         PrincipalId    => undef,
1146         @_
1147     );
1148
1149     # Load the relevant group. 
1150     my $group = RT::Group->new($self->CurrentUser);
1151     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1152     # Ask if it has the member in question
1153
1154     my $principal = RT::Principal->new($self->CurrentUser);
1155     $principal->Load($args{'PrincipalId'});
1156     unless ($principal->Id) {
1157         return (undef);
1158     }
1159
1160     return ($group->HasMemberRecursively($principal));
1161 }
1162
1163
1164
1165
1166 =head2 IsCc PRINCIPAL_ID
1167
1168 Takes an RT::Principal id.
1169 Returns true if the principal is a requestor of the current queue.
1170
1171
1172 =cut
1173
1174 sub IsCc {
1175     my $self = shift;
1176     my $cc   = shift;
1177
1178     return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1179
1180 }
1181
1182
1183
1184 =head2 IsAdminCc PRINCIPAL_ID
1185
1186 Takes an RT::Principal id.
1187 Returns true if the principal is a requestor of the current queue.
1188
1189 =cut
1190
1191 sub IsAdminCc {
1192     my $self   = shift;
1193     my $person = shift;
1194
1195     return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1196
1197 }
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208 sub _Set {
1209     my $self = shift;
1210
1211     unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1212         return ( 0, $self->loc('Permission Denied') );
1213     }
1214     return ( $self->SUPER::_Set(@_) );
1215 }
1216
1217
1218
1219 sub _Value {
1220     my $self = shift;
1221
1222     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1223         return (undef);
1224     }
1225
1226     return ( $self->__Value(@_) );
1227 }
1228
1229
1230
1231 =head2 CurrentUserHasRight
1232
1233 Takes one argument. A textual string with the name of the right we want to check.
1234 Returns true if the current user has that right for this queue.
1235 Returns undef otherwise.
1236
1237 =cut
1238
1239 sub CurrentUserHasRight {
1240     my $self  = shift;
1241     my $right = shift;
1242
1243     return (
1244         $self->HasRight(
1245             Principal => $self->CurrentUser,
1246             Right     => "$right"
1247           )
1248     );
1249
1250 }
1251
1252
1253
1254 =head2 HasRight
1255
1256 Takes a param hash with the fields 'Right' and 'Principal'.
1257 Principal defaults to the current user.
1258 Returns true if the principal has that right for this queue.
1259 Returns undef otherwise.
1260
1261 =cut
1262
1263 # TAKES: Right and optional "Principal" which defaults to the current user
1264 sub HasRight {
1265     my $self = shift;
1266     my %args = (
1267         Right     => undef,
1268         Principal => $self->CurrentUser,
1269         @_
1270     );
1271     my $principal = delete $args{'Principal'};
1272     unless ( $principal ) {
1273         $RT::Logger->error("Principal undefined in Queue::HasRight");
1274         return undef;
1275     }
1276
1277     return $principal->HasRight(
1278         %args,
1279         Object => ($self->Id ? $self : $RT::System),
1280     );
1281 }
1282
1283
1284
1285
1286 =head2 id
1287
1288 Returns the current value of id. 
1289 (In the database, id is stored as int(11).)
1290
1291
1292 =cut
1293
1294
1295 =head2 Name
1296
1297 Returns the current value of Name. 
1298 (In the database, Name is stored as varchar(200).)
1299
1300
1301
1302 =head2 SetName VALUE
1303
1304
1305 Set Name to VALUE. 
1306 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1307 (In the database, Name will be stored as a varchar(200).)
1308
1309
1310 =cut
1311
1312
1313 =head2 Description
1314
1315 Returns the current value of Description. 
1316 (In the database, Description is stored as varchar(255).)
1317
1318
1319
1320 =head2 SetDescription VALUE
1321
1322
1323 Set Description to VALUE. 
1324 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1325 (In the database, Description will be stored as a varchar(255).)
1326
1327
1328 =cut
1329
1330
1331 =head2 CorrespondAddress
1332
1333 Returns the current value of CorrespondAddress. 
1334 (In the database, CorrespondAddress is stored as varchar(120).)
1335
1336
1337
1338 =head2 SetCorrespondAddress VALUE
1339
1340
1341 Set CorrespondAddress to VALUE. 
1342 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1343 (In the database, CorrespondAddress will be stored as a varchar(120).)
1344
1345
1346 =cut
1347
1348
1349 =head2 CommentAddress
1350
1351 Returns the current value of CommentAddress. 
1352 (In the database, CommentAddress is stored as varchar(120).)
1353
1354
1355
1356 =head2 SetCommentAddress VALUE
1357
1358
1359 Set CommentAddress to VALUE. 
1360 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1361 (In the database, CommentAddress will be stored as a varchar(120).)
1362
1363
1364 =cut
1365
1366
1367 =head2 Lifecycle
1368
1369 Returns the current value of Lifecycle. 
1370 (In the database, Lifecycle is stored as varchar(32).)
1371
1372
1373
1374 =head2 SetLifecycle VALUE
1375
1376
1377 Set Lifecycle to VALUE. 
1378 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1379 (In the database, Lifecycle will be stored as a varchar(32).)
1380
1381
1382 =cut
1383
1384 =head2 SubjectTag
1385
1386 Returns the current value of SubjectTag. 
1387 (In the database, SubjectTag is stored as varchar(120).)
1388
1389
1390
1391 =head2 SetSubjectTag VALUE
1392
1393
1394 Set SubjectTag to VALUE. 
1395 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1396 (In the database, SubjectTag will be stored as a varchar(120).)
1397
1398
1399 =cut
1400
1401
1402 =head2 InitialPriority
1403
1404 Returns the current value of InitialPriority. 
1405 (In the database, InitialPriority is stored as int(11).)
1406
1407
1408
1409 =head2 SetInitialPriority VALUE
1410
1411
1412 Set InitialPriority to VALUE. 
1413 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1414 (In the database, InitialPriority will be stored as a int(11).)
1415
1416
1417 =cut
1418
1419
1420 =head2 FinalPriority
1421
1422 Returns the current value of FinalPriority. 
1423 (In the database, FinalPriority is stored as int(11).)
1424
1425
1426
1427 =head2 SetFinalPriority VALUE
1428
1429
1430 Set FinalPriority to VALUE. 
1431 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1432 (In the database, FinalPriority will be stored as a int(11).)
1433
1434
1435 =cut
1436
1437
1438 =head2 DefaultDueIn
1439
1440 Returns the current value of DefaultDueIn. 
1441 (In the database, DefaultDueIn is stored as int(11).)
1442
1443
1444
1445 =head2 SetDefaultDueIn VALUE
1446
1447
1448 Set DefaultDueIn to VALUE. 
1449 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1450 (In the database, DefaultDueIn will be stored as a int(11).)
1451
1452
1453 =cut
1454
1455
1456 =head2 Creator
1457
1458 Returns the current value of Creator. 
1459 (In the database, Creator is stored as int(11).)
1460
1461
1462 =cut
1463
1464
1465 =head2 Created
1466
1467 Returns the current value of Created. 
1468 (In the database, Created is stored as datetime.)
1469
1470
1471 =cut
1472
1473
1474 =head2 LastUpdatedBy
1475
1476 Returns the current value of LastUpdatedBy. 
1477 (In the database, LastUpdatedBy is stored as int(11).)
1478
1479
1480 =cut
1481
1482
1483 =head2 LastUpdated
1484
1485 Returns the current value of LastUpdated. 
1486 (In the database, LastUpdated is stored as datetime.)
1487
1488
1489 =cut
1490
1491
1492 =head2 Disabled
1493
1494 Returns the current value of Disabled. 
1495 (In the database, Disabled is stored as smallint(6).)
1496
1497
1498
1499 =head2 SetDisabled VALUE
1500
1501
1502 Set Disabled to VALUE. 
1503 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1504 (In the database, Disabled will be stored as a smallint(6).)
1505
1506
1507 =cut
1508
1509
1510
1511 sub _CoreAccessible {
1512     {
1513      
1514         id =>
1515         {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1516         Name => 
1517         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
1518         Description => 
1519         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1520         CorrespondAddress => 
1521         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1522         CommentAddress => 
1523         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1524         SubjectTag => 
1525         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1526         Lifecycle => 
1527         {read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
1528         InitialPriority => 
1529         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1530         FinalPriority => 
1531         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1532         DefaultDueIn => 
1533         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1534         Creator => 
1535         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1536         Created => 
1537         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1538         LastUpdatedBy => 
1539         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1540         LastUpdated => 
1541         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1542         Disabled => 
1543         {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
1544
1545  }
1546 };
1547
1548
1549
1550 RT::Base->_ImportOverlays();
1551
1552 1;