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