Merge branch 'patch-1' of https://github.com/gjones2/Freeside
[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         SignAuto          => undef,
398         Encrypt           => undef,
399         _RecordTransaction => 1,
400         @_
401     );
402
403     unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
404     {    #Check them ACLs
405         return ( 0, $self->loc("No permission to create queues") );
406     }
407
408     {
409         my ($val, $msg) = $self->_ValidateName( $args{'Name'} );
410         return ($val, $msg) unless $val;
411     }
412
413     if ( $args{'Lifecycle'} && $args{'Lifecycle'} ne 'default' ) {
414         return ( 0, $self->loc('Invalid lifecycle name') )
415             unless $self->ValidateLifecycle( $args{'Lifecycle'} );
416     } else {
417         $args{'Lifecycle'} = undef;
418     }
419
420     my %attrs = map {$_ => 1} $self->ReadableAttributes;
421
422     #TODO better input validation
423     $RT::Handle->BeginTransaction();
424     my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs );
425     unless ($id) {
426         $RT::Handle->Rollback();
427         return ( 0, $self->loc('Queue could not be created') );
428     }
429
430     my $create_ret = $self->_CreateQueueGroups();
431     unless ($create_ret) {
432         $RT::Handle->Rollback();
433         return ( 0, $self->loc('Queue could not be created') );
434     }
435     if ( $args{'_RecordTransaction'} ) {
436         $self->_NewTransaction( Type => "Create" );
437     }
438     $RT::Handle->Commit;
439
440     for my $attr (qw/Sign SignAuto Encrypt/) {
441         next unless defined $args{$attr};
442         my $set = "Set" . $attr;
443         my ($status, $msg) = $self->$set( $args{$attr} );
444         $RT::Logger->error("Couldn't set attribute '$attr': $msg")
445             unless $status;
446     }
447
448     RT->System->QueueCacheNeedsUpdate(1);
449
450     return ( $id, $self->loc("Queue created") );
451 }
452
453
454
455 sub Delete {
456     my $self = shift;
457     return ( 0,
458         $self->loc('Deleting this object would break referential integrity') );
459 }
460
461
462
463 =head2 SetDisabled
464
465 Takes a boolean.
466 1 will cause this queue to no longer be available for tickets.
467 0 will re-enable this queue.
468
469 =cut
470
471 sub SetDisabled {
472     my $self = shift;
473     my $val = shift;
474
475     $RT::Handle->BeginTransaction();
476     my $set_err = $self->_Set( Field =>'Disabled', Value => $val);
477     unless ($set_err) {
478         $RT::Handle->Rollback();
479         $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." queue ".$self->PrincipalObj->Id);
480         return (undef);
481     }
482     $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
483
484     $RT::Handle->Commit();
485
486     RT->System->QueueCacheNeedsUpdate(1);
487
488     if ( $val == 1 ) {
489         return (1, $self->loc("Queue disabled"));
490     } else {
491         return (1, $self->loc("Queue enabled"));
492     }
493
494 }
495
496
497
498 =head2 Load
499
500 Takes either a numerical id or a textual Name and loads the specified queue.
501
502 =cut
503
504 sub Load {
505     my $self = shift;
506
507     my $identifier = shift;
508     if ( !$identifier ) {
509         return (undef);
510     }
511
512     if ( $identifier =~ /^(\d+)$/ ) {
513         $self->SUPER::LoadById($identifier);
514     }
515     else {
516         $self->LoadByCols( Name => $identifier );
517     }
518
519     return ( $self->Id );
520
521 }
522
523
524
525 =head2 ValidateName NAME
526
527 Takes a queue name. Returns true if it's an ok name for
528 a new queue. Returns undef if there's already a queue by that name.
529
530 =cut
531
532 sub ValidateName {
533     my $self = shift;
534     my $name = shift;
535
536     my ($ok, $msg) = $self->_ValidateName($name);
537
538     return $ok ? 1 : 0;
539 }
540
541 sub _ValidateName {
542     my $self = shift;
543     my $name = shift;
544
545     return (undef, "Queue name is required") unless length $name;
546
547     # Validate via the superclass first
548     # Case: short circuit if it's an integer so we don't have
549     # fale negatives when loading a temp queue
550     unless ( my $q = $self->SUPER::ValidateName($name) ) {
551         return ($q, $self->loc("'[_1]' is not a valid name.", $name));
552     }
553
554     my $tempqueue = RT::Queue->new(RT->SystemUser);
555     $tempqueue->Load($name);
556
557     #If this queue exists, return undef
558     if ( $tempqueue->Name() && $tempqueue->id != $self->id)  {
559         return (undef, $self->loc("Queue already exists") );
560     }
561
562     return (1);
563 }
564
565
566 =head2 SetSign
567
568 =cut
569
570 sub Sign {
571     my $self = shift;
572     my $value = shift;
573
574     return undef unless $self->CurrentUserHasRight('SeeQueue');
575     my $attr = $self->FirstAttribute('Sign') or return 0;
576     return $attr->Content;
577 }
578
579 sub SetSign {
580     my $self = shift;
581     my $value = shift;
582
583     return ( 0, $self->loc('Permission Denied') )
584         unless $self->CurrentUserHasRight('AdminQueue');
585
586     my ($status, $msg) = $self->SetAttribute(
587         Name        => 'Sign',
588         Description => 'Sign outgoing messages by default',
589         Content     => $value,
590     );
591     return ($status, $msg) unless $status;
592     return ($status, $self->loc('Signing enabled')) if $value;
593     return ($status, $self->loc('Signing disabled'));
594 }
595
596 sub SignAuto {
597     my $self = shift;
598     my $value = shift;
599
600     return undef unless $self->CurrentUserHasRight('SeeQueue');
601     my $attr = $self->FirstAttribute('SignAuto') or return 0;
602     return $attr->Content;
603 }
604
605 sub SetSignAuto {
606     my $self = shift;
607     my $value = shift;
608
609     return ( 0, $self->loc('Permission Denied') )
610         unless $self->CurrentUserHasRight('AdminQueue');
611
612     my ($status, $msg) = $self->SetAttribute(
613         Name        => 'SignAuto',
614         Description => 'Sign auto-generated outgoing messages',
615         Content     => $value,
616     );
617     return ($status, $msg) unless $status;
618     return ($status, $self->loc('Signing enabled')) if $value;
619     return ($status, $self->loc('Signing disabled'));
620 }
621
622 sub Encrypt {
623     my $self = shift;
624     my $value = shift;
625
626     return undef unless $self->CurrentUserHasRight('SeeQueue');
627     my $attr = $self->FirstAttribute('Encrypt') or return 0;
628     return $attr->Content;
629 }
630
631 sub SetEncrypt {
632     my $self = shift;
633     my $value = shift;
634
635     return ( 0, $self->loc('Permission Denied') )
636         unless $self->CurrentUserHasRight('AdminQueue');
637
638     my ($status, $msg) = $self->SetAttribute(
639         Name        => 'Encrypt',
640         Description => 'Encrypt outgoing messages by default',
641         Content     => $value,
642     );
643     return ($status, $msg) unless $status;
644     return ($status, $self->loc('Encrypting enabled')) if $value;
645     return ($status, $self->loc('Encrypting disabled'));
646 }
647
648 =head2 Templates
649
650 Returns an RT::Templates object of all of this queue's templates.
651
652 =cut
653
654 sub Templates {
655     my $self = shift;
656
657     my $templates = RT::Templates->new( $self->CurrentUser );
658
659     if ( $self->CurrentUserHasRight('ShowTemplate') ) {
660         $templates->LimitToQueue( $self->id );
661     }
662
663     return ($templates);
664 }
665
666
667
668
669 =head2 CustomField NAME
670
671 Load the queue-specific custom field named NAME
672
673 =cut
674
675 sub CustomField {
676     my $self = shift;
677     my $name = shift;
678     my $cf = RT::CustomField->new($self->CurrentUser);
679     $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id); 
680     return ($cf);
681 }
682
683
684
685 =head2 TicketCustomFields
686
687 Returns an L<RT::CustomFields> object containing all global and
688 queue-specific B<ticket> custom fields.
689
690 =cut
691
692 sub TicketCustomFields {
693     my $self = shift;
694
695     my $cfs = RT::CustomFields->new( $self->CurrentUser );
696     if ( $self->CurrentUserHasRight('SeeQueue') ) {
697         $cfs->SetContextObject( $self );
698         $cfs->LimitToGlobalOrObjectId( $self->Id );
699         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' );
700         $cfs->ApplySortOrder;
701     }
702     return ($cfs);
703 }
704
705
706
707 =head2 TicketTransactionCustomFields
708
709 Returns an L<RT::CustomFields> object containing all global and
710 queue-specific B<transaction> custom fields.
711
712 =cut
713
714 sub TicketTransactionCustomFields {
715     my $self = shift;
716
717     my $cfs = RT::CustomFields->new( $self->CurrentUser );
718     if ( $self->CurrentUserHasRight('SeeQueue') ) {
719         $cfs->SetContextObject( $self );
720         $cfs->LimitToGlobalOrObjectId( $self->Id );
721         $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
722         $cfs->ApplySortOrder;
723     }
724     return ($cfs);
725 }
726
727
728
729
730
731 =head2 AllRoleGroupTypes
732
733 Returns a list of the names of the various role group types that this queue
734 has, including Requestor and Owner. If you don't want them, see
735 L</ManageableRoleGroupTypes>.
736
737 =cut
738
739 sub AllRoleGroupTypes {
740     my $self = shift;
741     return ($self->ManageableRoleGroupTypes, qw(Requestor Owner));
742 }
743
744 =head2 IsRoleGroupType
745
746 Returns whether the passed-in type is a role group type.
747
748 =cut
749
750 sub IsRoleGroupType {
751     my $self = shift;
752     my $type = shift;
753
754     for my $valid_type ($self->AllRoleGroupTypes) {
755         return 1 if $type eq $valid_type;
756     }
757
758     return 0;
759 }
760
761 =head2 ManageableRoleGroupTypes
762
763 Returns a list of the names of the various role group types that this queue
764 has, excluding Requestor and Owner. If you want them, see L</AllRoleGroupTypes>.
765
766 =cut
767
768 sub ManageableRoleGroupTypes {
769     return qw(Cc AdminCc);
770 }
771
772 =head2 IsManageableRoleGroupType
773
774 Returns whether the passed-in type is a manageable role group type.
775
776 =cut
777
778 sub IsManageableRoleGroupType {
779     my $self = shift;
780     my $type = shift;
781
782     for my $valid_type ($self->ManageableRoleGroupTypes) {
783         return 1 if $type eq $valid_type;
784     }
785
786     return 0;
787 }
788
789
790 =head2 _CreateQueueGroups
791
792 Create the ticket groups and links for this ticket. 
793 This routine expects to be called from Ticket->Create _inside of a transaction_
794
795 It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
796
797 It will return true on success and undef on failure.
798
799
800 =cut
801
802 sub _CreateQueueGroups {
803     my $self = shift;
804
805     my @types = $self->AllRoleGroupTypes;
806
807     foreach my $type (@types) {
808         my $ok = $self->_CreateQueueRoleGroup($type);
809         return undef if !$ok;
810     }
811
812     return 1;
813 }
814
815 sub _CreateQueueRoleGroup {
816     my $self = shift;
817     my $type = shift;
818
819     my $type_obj = RT::Group->new($self->CurrentUser);
820     my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id, 
821                                                     Type => $type,
822                                                     Domain => 'RT::Queue-Role');
823     unless ($id) {
824         $RT::Logger->error("Couldn't create a Queue group of type '$type' for queue ".
825                             $self->Id.": ".$msg);
826         return(undef);
827     }
828
829     return $id;
830 }
831
832
833
834 # _HasModifyWatcherRight {{{
835 sub _HasModifyWatcherRight {
836     my $self = shift;
837     my %args = (
838         Type  => undef,
839         PrincipalId => undef,
840         Email => undef,
841         @_
842     );
843
844     return 1 if $self->CurrentUserHasRight('ModifyQueueWatchers');
845
846     #If the watcher we're trying to add is for the current user
847     if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
848         if ( $args{'Type'} eq 'AdminCc' ) {
849             return 1 if $self->CurrentUserHasRight('WatchAsAdminCc');
850         }
851         elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
852             return 1 if $self->CurrentUserHasRight('Watch');
853         }
854         else {
855             $RT::Logger->warning( "$self -> _HasModifyWatcher got passed a bogus type $args{Type}");
856             return ( 0, $self->loc('Invalid queue role group type [_1]', $args{Type}) );
857         }
858     }
859
860     return ( 0, $self->loc("Permission Denied") );
861 }
862
863
864 =head2 AddWatcher
865
866 AddWatcher takes a parameter hash. The keys are as follows:
867
868 Type        One of Requestor, Cc, AdminCc
869
870 PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
871 Email       The email address of the new watcher. If a user with this 
872             email address can't be found, a new nonprivileged user will be created.
873
874 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.
875
876 Returns a tuple of (status/id, message).
877
878 =cut
879
880 sub AddWatcher {
881     my $self = shift;
882     my %args = (
883         Type  => undef,
884         PrincipalId => undef,
885         Email => undef,
886         @_
887     );
888
889     return ( 0, "No principal specified" )
890         unless $args{'Email'} or $args{'PrincipalId'};
891
892     if ( !$args{'PrincipalId'} && $args{'Email'} ) {
893         my $user = RT::User->new( $self->CurrentUser );
894         $user->LoadByEmail( $args{'Email'} );
895         $args{'PrincipalId'} = $user->PrincipalId if $user->id;
896     }
897
898     return ( 0, "Unknown watcher type [_1]", $args{Type} )
899         unless $self->IsRoleGroupType($args{Type});
900
901     my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
902     return ($ok, $msg) if !$ok;
903
904     return $self->_AddWatcher(%args);
905 }
906
907 #This contains the meat of AddWatcher. but can be called from a routine like
908 # Create, which doesn't need the additional acl check
909 sub _AddWatcher {
910     my $self = shift;
911     my %args = (
912         Type   => undef,
913         Silent => undef,
914         PrincipalId => undef,
915         Email => undef,
916         @_
917     );
918
919
920     my $principal = RT::Principal->new( $self->CurrentUser );
921     if ( $args{'PrincipalId'} ) {
922         $principal->Load( $args{'PrincipalId'} );
923         if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
924             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'})))
925                 if RT::EmailParser->IsRTAddress( $email );
926         }
927     }
928     elsif ( $args{'Email'} ) {
929         if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
930             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'})));
931         }
932         my $user = RT::User->new($self->CurrentUser);
933         $user->LoadByEmail( $args{'Email'} );
934         $user->Load( $args{'Email'} )
935             unless $user->id;
936
937         if ( $user->Id ) { # If the user exists
938             $principal->Load( $user->PrincipalId );
939         } else {
940             # if the user doesn't exist, we need to create a new user
941             my $new_user = RT::User->new(RT->SystemUser);
942
943             my ( $Address, $Name ) =  
944                RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
945
946             my ( $Val, $Message ) = $new_user->Create(
947                 Name         => $Address,
948                 EmailAddress => $Address,
949                 RealName     => $Name,
950                 Privileged   => 0,
951                 Comments     => 'Autocreated when added as a watcher'
952             );
953             unless ($Val) {
954                 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
955                 # Deal with the race condition of two account creations at once
956                 $new_user->LoadByEmail( $args{'Email'} );
957             }
958             $principal->Load( $new_user->PrincipalId );
959         }
960     }
961     # If we can't find this watcher, we need to bail.
962     unless ( $principal->Id ) {
963         return(0, $self->loc("Could not find or create that user"));
964     }
965
966     my $group = RT::Group->new($self->CurrentUser);
967     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
968     unless ($group->id) {
969         return(0,$self->loc("Group not found"));
970     }
971
972     if ( $group->HasMember( $principal)) {
973
974         return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
975     }
976
977
978     my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
979     unless ($m_id) {
980         $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
981
982         return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
983     }
984     return ( 1, $self->loc("Added [_1] to members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
985 }
986
987
988
989 =head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
990
991
992 Deletes a queue  watcher.  Takes two arguments:
993
994 Type  (one of Requestor,Cc,AdminCc)
995
996 and one of
997
998 PrincipalId (an RT::Principal Id of the watcher you want to remove)
999     OR
1000 Email (the email address of an existing wathcer)
1001
1002
1003 =cut
1004
1005
1006 sub DeleteWatcher {
1007     my $self = shift;
1008
1009     my %args = ( Type => undef,
1010                  PrincipalId => undef,
1011                  Email => undef,
1012                  @_ );
1013
1014     unless ( $args{'PrincipalId'} || $args{'Email'} ) {
1015         return ( 0, $self->loc("No principal specified") );
1016     }
1017
1018     if ( !$args{PrincipalId} and $args{Email} ) {
1019         my $user = RT::User->new( $self->CurrentUser );
1020         my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
1021         $args{PrincipalId} = $user->PrincipalId if $rv;
1022     }
1023     
1024     my $principal = RT::Principal->new( $self->CurrentUser );
1025     if ( $args{'PrincipalId'} ) {
1026         $principal->Load( $args{'PrincipalId'} );
1027     }
1028     else {
1029         my $user = RT::User->new( $self->CurrentUser );
1030         $user->LoadByEmail( $args{'Email'} );
1031         $principal->Load( $user->Id );
1032     }
1033
1034     # If we can't find this watcher, we need to bail.
1035     unless ( $principal->Id ) {
1036         return ( 0, $self->loc("Could not find that principal") );
1037     }
1038
1039     my $group = RT::Group->new($self->CurrentUser);
1040     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
1041     unless ($group->id) {
1042         return(0,$self->loc("Group not found"));
1043     }
1044
1045     return ( 0, $self->loc('Unknown watcher type [_1]', $args{Type}) )
1046         unless $self->IsRoleGroupType($args{Type});
1047
1048     my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
1049     return ($ok, $msg) if !$ok;
1050
1051     # see if this user is already a watcher.
1052
1053     unless ( $group->HasMember($principal)) {
1054         return ( 0,
1055         $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
1056     }
1057
1058     my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
1059     unless ($m_id) {
1060         $RT::Logger->error("Failed to delete ".$principal->Id.
1061                            " as a member of group ".$group->Id.": ".$m_msg);
1062
1063         return ( 0,    $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
1064     }
1065
1066     return ( 1, $self->loc("Removed [_1] from members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
1067 }
1068
1069
1070
1071 =head2 AdminCcAddresses
1072
1073 returns String: All queue AdminCc email addresses as a string
1074
1075 =cut
1076
1077 sub AdminCcAddresses {
1078     my $self = shift;
1079     
1080     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1081         return undef;
1082     }   
1083     
1084     return ( $self->AdminCc->MemberEmailAddressesAsString )
1085     
1086 }   
1087
1088
1089
1090 =head2 CcAddresses
1091
1092 returns String: All queue Ccs as a string of email addresses
1093
1094 =cut
1095
1096 sub CcAddresses {
1097     my $self = shift;
1098
1099     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1100         return undef;
1101     }
1102
1103     return ( $self->Cc->MemberEmailAddressesAsString);
1104
1105 }
1106
1107
1108
1109 =head2 Cc
1110
1111 Takes nothing.
1112 Returns an RT::Group object which contains this Queue's Ccs.
1113 If the user doesn't have "ShowQueue" permission, returns an empty group
1114
1115 =cut
1116
1117 sub Cc {
1118     my $self = shift;
1119
1120     my $group = RT::Group->new($self->CurrentUser);
1121     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1122         $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1123     }
1124     return ($group);
1125
1126 }
1127
1128
1129
1130 =head2 AdminCc
1131
1132 Takes nothing.
1133 Returns an RT::Group object which contains this Queue's AdminCcs.
1134 If the user doesn't have "ShowQueue" permission, returns an empty group
1135
1136 =cut
1137
1138 sub AdminCc {
1139     my $self = shift;
1140
1141     my $group = RT::Group->new($self->CurrentUser);
1142     if ( $self->CurrentUserHasRight('SeeQueue') ) {
1143         $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1144     }
1145     return ($group);
1146
1147 }
1148
1149
1150
1151 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1152
1153 =head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1154
1155 Takes a param hash with the attributes Type and PrincipalId
1156
1157 Type is one of Requestor, Cc, AdminCc and Owner
1158
1159 PrincipalId is an RT::Principal id 
1160
1161 Returns true if that principal is a member of the group Type for this queue
1162
1163
1164 =cut
1165
1166 sub IsWatcher {
1167     my $self = shift;
1168
1169     my %args = ( Type  => 'Cc',
1170         PrincipalId    => undef,
1171         @_
1172     );
1173
1174     # Load the relevant group. 
1175     my $group = RT::Group->new($self->CurrentUser);
1176     $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1177     # Ask if it has the member in question
1178
1179     my $principal = RT::Principal->new($self->CurrentUser);
1180     $principal->Load($args{'PrincipalId'});
1181     unless ($principal->Id) {
1182         return (undef);
1183     }
1184
1185     return ($group->HasMemberRecursively($principal));
1186 }
1187
1188
1189
1190
1191 =head2 IsCc PRINCIPAL_ID
1192
1193 Takes an RT::Principal id.
1194 Returns true if the principal is a requestor of the current queue.
1195
1196
1197 =cut
1198
1199 sub IsCc {
1200     my $self = shift;
1201     my $cc   = shift;
1202
1203     return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1204
1205 }
1206
1207
1208
1209 =head2 IsAdminCc PRINCIPAL_ID
1210
1211 Takes an RT::Principal id.
1212 Returns true if the principal is a requestor of the current queue.
1213
1214 =cut
1215
1216 sub IsAdminCc {
1217     my $self   = shift;
1218     my $person = shift;
1219
1220     return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1221
1222 }
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233 sub _Set {
1234     my $self = shift;
1235
1236     unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1237         return ( 0, $self->loc('Permission Denied') );
1238     }
1239     return ( $self->SUPER::_Set(@_) );
1240 }
1241
1242
1243
1244 sub _Value {
1245     my $self = shift;
1246
1247     unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1248         return (undef);
1249     }
1250
1251     return ( $self->__Value(@_) );
1252 }
1253
1254
1255
1256 =head2 CurrentUserHasRight
1257
1258 Takes one argument. A textual string with the name of the right we want to check.
1259 Returns true if the current user has that right for this queue.
1260 Returns undef otherwise.
1261
1262 =cut
1263
1264 sub CurrentUserHasRight {
1265     my $self  = shift;
1266     my $right = shift;
1267
1268     return (
1269         $self->HasRight(
1270             Principal => $self->CurrentUser,
1271             Right     => "$right"
1272           )
1273     );
1274
1275 }
1276
1277 =head2 CurrentUserCanSee
1278
1279 Returns true if the current user can see the queue, using SeeQueue
1280
1281 =cut
1282
1283 sub CurrentUserCanSee {
1284     my $self = shift;
1285
1286     return $self->CurrentUserHasRight('SeeQueue');
1287 }
1288
1289
1290 =head2 HasRight
1291
1292 Takes a param hash with the fields 'Right' and 'Principal'.
1293 Principal defaults to the current user.
1294 Returns true if the principal has that right for this queue.
1295 Returns undef otherwise.
1296
1297 =cut
1298
1299 # TAKES: Right and optional "Principal" which defaults to the current user
1300 sub HasRight {
1301     my $self = shift;
1302     my %args = (
1303         Right     => undef,
1304         Principal => $self->CurrentUser,
1305         @_
1306     );
1307     my $principal = delete $args{'Principal'};
1308     unless ( $principal ) {
1309         $RT::Logger->error("Principal undefined in Queue::HasRight");
1310         return undef;
1311     }
1312
1313     return $principal->HasRight(
1314         %args,
1315         Object => ($self->Id ? $self : $RT::System),
1316     );
1317 }
1318
1319
1320
1321
1322 =head2 id
1323
1324 Returns the current value of id. 
1325 (In the database, id is stored as int(11).)
1326
1327
1328 =cut
1329
1330
1331 =head2 Name
1332
1333 Returns the current value of Name. 
1334 (In the database, Name is stored as varchar(200).)
1335
1336
1337
1338 =head2 SetName VALUE
1339
1340
1341 Set Name to VALUE. 
1342 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1343 (In the database, Name will be stored as a varchar(200).)
1344
1345
1346 =cut
1347
1348
1349 =head2 Description
1350
1351 Returns the current value of Description. 
1352 (In the database, Description is stored as varchar(255).)
1353
1354
1355
1356 =head2 SetDescription VALUE
1357
1358
1359 Set Description to VALUE. 
1360 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1361 (In the database, Description will be stored as a varchar(255).)
1362
1363
1364 =cut
1365
1366
1367 =head2 CorrespondAddress
1368
1369 Returns the current value of CorrespondAddress. 
1370 (In the database, CorrespondAddress is stored as varchar(120).)
1371
1372
1373
1374 =head2 SetCorrespondAddress VALUE
1375
1376
1377 Set CorrespondAddress to VALUE. 
1378 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1379 (In the database, CorrespondAddress will be stored as a varchar(120).)
1380
1381
1382 =cut
1383
1384
1385 =head2 CommentAddress
1386
1387 Returns the current value of CommentAddress. 
1388 (In the database, CommentAddress is stored as varchar(120).)
1389
1390
1391
1392 =head2 SetCommentAddress VALUE
1393
1394
1395 Set CommentAddress to VALUE. 
1396 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1397 (In the database, CommentAddress will be stored as a varchar(120).)
1398
1399
1400 =cut
1401
1402
1403 =head2 Lifecycle
1404
1405 Returns the current value of Lifecycle. 
1406 (In the database, Lifecycle is stored as varchar(32).)
1407
1408
1409
1410 =head2 SetLifecycle VALUE
1411
1412
1413 Set Lifecycle to VALUE. 
1414 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1415 (In the database, Lifecycle will be stored as a varchar(32).)
1416
1417
1418 =cut
1419
1420 =head2 SubjectTag
1421
1422 Returns the current value of SubjectTag. 
1423 (In the database, SubjectTag is stored as varchar(120).)
1424
1425
1426
1427 =head2 SetSubjectTag VALUE
1428
1429
1430 Set SubjectTag to VALUE. 
1431 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1432 (In the database, SubjectTag will be stored as a varchar(120).)
1433
1434
1435 =cut
1436
1437
1438 =head2 InitialPriority
1439
1440 Returns the current value of InitialPriority. 
1441 (In the database, InitialPriority is stored as int(11).)
1442
1443
1444
1445 =head2 SetInitialPriority VALUE
1446
1447
1448 Set InitialPriority to VALUE. 
1449 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1450 (In the database, InitialPriority will be stored as a int(11).)
1451
1452
1453 =cut
1454
1455
1456 =head2 FinalPriority
1457
1458 Returns the current value of FinalPriority. 
1459 (In the database, FinalPriority is stored as int(11).)
1460
1461
1462
1463 =head2 SetFinalPriority VALUE
1464
1465
1466 Set FinalPriority to VALUE. 
1467 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1468 (In the database, FinalPriority will be stored as a int(11).)
1469
1470
1471 =cut
1472
1473
1474 =head2 DefaultDueIn
1475
1476 Returns the current value of DefaultDueIn. 
1477 (In the database, DefaultDueIn is stored as int(11).)
1478
1479
1480
1481 =head2 SetDefaultDueIn VALUE
1482
1483
1484 Set DefaultDueIn to VALUE. 
1485 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1486 (In the database, DefaultDueIn will be stored as a int(11).)
1487
1488
1489 =cut
1490
1491
1492 =head2 Creator
1493
1494 Returns the current value of Creator. 
1495 (In the database, Creator is stored as int(11).)
1496
1497
1498 =cut
1499
1500
1501 =head2 Created
1502
1503 Returns the current value of Created. 
1504 (In the database, Created is stored as datetime.)
1505
1506
1507 =cut
1508
1509
1510 =head2 LastUpdatedBy
1511
1512 Returns the current value of LastUpdatedBy. 
1513 (In the database, LastUpdatedBy is stored as int(11).)
1514
1515
1516 =cut
1517
1518
1519 =head2 LastUpdated
1520
1521 Returns the current value of LastUpdated. 
1522 (In the database, LastUpdated is stored as datetime.)
1523
1524
1525 =cut
1526
1527
1528 =head2 Disabled
1529
1530 Returns the current value of Disabled. 
1531 (In the database, Disabled is stored as smallint(6).)
1532
1533
1534
1535 =head2 SetDisabled VALUE
1536
1537
1538 Set Disabled to VALUE. 
1539 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1540 (In the database, Disabled will be stored as a smallint(6).)
1541
1542
1543 =cut
1544
1545
1546
1547 sub _CoreAccessible {
1548     {
1549      
1550         id =>
1551         {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1552         Name => 
1553         {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
1554         Description => 
1555         {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1556         CorrespondAddress => 
1557         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1558         CommentAddress => 
1559         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1560         SubjectTag => 
1561         {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
1562         Lifecycle => 
1563         {read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
1564         InitialPriority => 
1565         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1566         FinalPriority => 
1567         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1568         DefaultDueIn => 
1569         {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1570         Creator => 
1571         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1572         Created => 
1573         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1574         LastUpdatedBy => 
1575         {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1576         LastUpdated => 
1577         {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1578         Disabled => 
1579         {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
1580
1581  }
1582 };
1583
1584
1585
1586 RT::Base->_ImportOverlays();
1587
1588 1;